TypeOfNaN

How To Fix Undefined 'this.state' in React Class Components

Nick Scialli
May 14, 2021

New — Check out my free newsletter on how the web works!

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:

count of 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.

🎓 Learn how the web works

One of the best ways to level up your tech career is to have a great foundational understanding of how the web works. In my free newsletter, How the Web Works, I provide simple, bite-sized explanations for various web topics that can help you boost your knowledge. Join 2,500+ other learners on the newsletter today!

Signing up is free, I never spam, and you can unsubscribe any time. You won't regret it!

Sign up for the newsletter »
Nick Scialli

Nick Scialli is a senior UI engineer at Microsoft.

© 2024 Nick Scialli