TypeOfNaN

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

Nick Scialli May 14, 2021🚀 4 minute read

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.

If you'd like to support this blog by buying me a coffee I'd really appreciate it!

Nick Scialli

Nick Scialli is a software engineer at the U.S. Digital Service.