How To Fix Undefined 'this.state' in React Class Components
Nick Scialli
May 14, 2021
If you’re working with React class components, you may find yourself vexed by ‘this.state’ being undefined
.
The Issue
You are likely trying to call an instance method in an event handler. Consider the following example of a basic counter that we can increment and decrement:
class App extends React.Component {
constructor() {
super();
this.state = {
count: 0,
};
}
increment() {
this.setState({ count: this.state.count + 1 });
}
decrement() {
this.setState({ count: this.state.count - 1 });
}
render() {
return (
<div className="App">
<h1>Counter</h1>
<div>{this.state.count}</div>
<button onClick={this.increment}>Increment</button>
<button onClick={this.decrement}>Decrement</button>
</div>
);
}
}
export default App;
When we click the Increment or Decrement button, we’ll get an error: Cannot read property 'count' of undefined
. Our console will show us that indeed it appears this.state
is undefined:
So why is this happening?
The Cause
React components using ES6 classes do not autobind this
to non-React.Component methods. That means we have no assurance that this
within our increment
and decrement
methods refer to the instance of the React class component.
The Solutions
Solution 1: binding in the constructor
We have a few different options. The most popular, and my favorite, is to immediately bind our methods to the React component instance in the constructor
method.
The result of this implementation looks like:
class App extends React.Component {
constructor() {
super();
this.state = {
count: 0,
};
this.increment = this.increment.bind(this);
this.decrement = this.decrement.bind(this);
}
increment() {
this.setState({ count: this.state.count + 1 });
}
decrement() {
this.setState({ count: this.state.count - 1 });
}
render() {
return (
<div className="App">
<h1>Counter</h1>
<div>{this.state.count}</div>
<button onClick={this.increment}>Increment</button>
<button onClick={this.decrement}>Decrement</button>
</div>
);
}
}
export default App;
Solution 2: binding in the event handler
Since we only call this.increment
and this.decrement
once, we can actually just pass bound versions of these functions in the button
event handlers themselves. This works but will quickly become inefficient and error-prone if you are going to call these methods in multiple places.
Here’s how this approach looks:
class App extends React.Component {
constructor() {
super();
this.state = {
count: 0,
};
}
increment() {
this.setState({ count: this.state.count + 1 });
}
decrement() {
this.setState({ count: this.state.count - 1 });
}
render() {
return (
<div className="App">
<h1>Counter</h1>
<div>{this.state.count}</div>
<button onClick={this.increment.bind(this)}>Increment</button>
<button onClick={this.decrement.bind(this)}>Decrement</button>
</div>
);
}
}
export default App;
Solution 3: change methods to arrow function class properties
The last option we have is to change our increment
and decrement
methods to arrow function class properties. This solution appears enticing on its face, but we’ll discuss why it’s my least favorite solution after we look at the implementation:
class App extends React.Component {
constructor() {
super();
this.state = {
count: 0,
};
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
decrement = () => {
this.setState({ count: this.state.count - 1 });
};
render() {
return (
<div className="App">
<h1>Counter</h1>
<div>{this.state.count}</div>
<button onClick={this.increment}>Increment</button>
<button onClick={this.decrement}>Decrement</button>
</div>
);
}
}
export default App;
This does work, but the big problem here is that increment
and decrement
are created each time a new instance of the component is created. In other words, they exist on the instance rather than the constructor function. This ends up being theoretically inefficient compared to the properties existing on the prototype.
Conclusion
In this post we discovered why React class components might appear to have undefined state and a few different ways to solve the issue.
Nick Scialli is a senior UI engineer at Microsoft.