others-How to resolve the ' Cannot read properties of undefined reading setState' issue when trying to access this.state in react components

1. Purpose

In this post, I would demo how solve the following issue when trying to access this.state in react components:

index.js:14 Uncaught TypeError: Cannot read properties of undefined (reading 'setState')
    at onClickMe (index.js:14)
    at HTMLUnknownElement.callCallback (react-dom.development.js:3945)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:3994)
    at invokeGuardedCallback (react-dom.development.js:4056)
    at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4070)
    at executeDispatch (react-dom.development.js:8243)
    at processDispatchQueueItemsInOrder (react-dom.development.js:8275)
    at processDispatchQueue (react-dom.development.js:8288)
    at dispatchEventsForPlugins (react-dom.development.js:8299)
    at react-dom.development.js:8508
    at batchedEventUpdates$1 (react-dom.development.js:22396)
    at batchedEventUpdates (react-dom.development.js:3745)
    at dispatchEventForPluginEventSystem (react-dom.development.js:8507)
    at attemptToDispatchEvent (react-dom.development.js:6005)
    at dispatchEvent (react-dom.development.js:5924)
    at unstable_runWithPriority (scheduler.development.js:468)
    at runWithPriority$1 (react-dom.development.js:11276)
    at discreteUpdates$1 (react-dom.development.js:22413)
    at discreteUpdates (react-dom.development.js:3756)
    at dispatchDiscreteEvent (react-dom.development.js:5889)
onClickMe @ index.js:14
callCallback @ react-dom.development.js:3945
invokeGuardedCallbackDev @ react-dom.development.js:3994
invokeGuardedCallback @ react-dom.development.js:4056
invokeGuardedCallbackAndCatchFirstError @ react-dom.development.js:4070
executeDispatch @ react-dom.development.js:8243
processDispatchQueueItemsInOrder @ react-dom.development.js:8275
processDispatchQueue @ react-dom.development.js:8288
dispatchEventsForPlugins @ react-dom.development.js:8299
(anonymous) @ react-dom.development.js:8508
batchedEventUpdates$1 @ react-dom.development.js:22396
batchedEventUpdates @ react-dom.development.js:3745
dispatchEventForPluginEventSystem @ react-dom.development.js:8507
attemptToDispatchEvent @ react-dom.development.js:6005
dispatchEvent @ react-dom.development.js:5924
unstable_runWithPriority @ scheduler.development.js:468
runWithPriority$1 @ react-dom.development.js:11276
discreteUpdates$1 @ react-dom.development.js:22413
discreteUpdates @ react-dom.development.js:3756
dispatchDiscreteEvent @ react-dom.development.js:5889
react-dom.development.js:4091 Uncaught TypeError: Cannot read properties of undefined (reading 'setState')
    at onClickMe (index.js:14)
    at HTMLUnknownElement.callCallback (react-dom.development.js:3945)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:3994)
    at invokeGuardedCallback (react-dom.development.js:4056)
    at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4070)
    at executeDispatch (react-dom.development.js:8243)
    at processDispatchQueueItemsInOrder (react-dom.development.js:8275)
    at processDispatchQueue (react-dom.development.js:8288)
    at dispatchEventsForPlugins (react-dom.development.js:8299)
    at react-dom.development.js:8508
    at batchedEventUpdates$1 (react-dom.development.js:22396)
    at batchedEventUpdates (react-dom.development.js:3745)
    at dispatchEventForPluginEventSystem (react-dom.development.js:8507)
    at attemptToDispatchEvent (react-dom.development.js:6005)
    at dispatchEvent (react-dom.development.js:5924)
    at unstable_runWithPriority (scheduler.development.js:468)
    at runWithPriority$1 (react-dom.development.js:11276)
    at discreteUpdates$1 (react-dom.development.js:22413)
    at discreteUpdates (react-dom.development.js:3756)
    at dispatchDiscreteEvent (react-dom.development.js:5889)
onClickMe @ index.js:14
callCallback @ react-dom.development.js:3945
invokeGuardedCallbackDev @ react-dom.development.js:3994
invokeGuardedCallback @ react-dom.development.js:4056
invokeGuardedCallbackAndCatchFirstError @ react-dom.development.js:4070
executeDispatch @ react-dom.development.js:8243
processDispatchQueueItemsInOrder @ react-dom.development.js:8275
processDispatchQueue @ react-dom.development.js:8288
dispatchEventsForPlugins @ react-dom.development.js:8299
(anonymous) @ react-dom.development.js:8508
batchedEventUpdates$1 @ react-dom.development.js:22396
batchedEventUpdates @ react-dom.development.js:3745
dispatchEventForPluginEventSystem @ react-dom.development.js:8507
attemptToDispatchEvent @ react-dom.development.js:6005
dispatchEvent @ react-dom.development.js:5924
unstable_runWithPriority @ scheduler.development.js:468
runWithPriority$1 @ react-dom.development.js:11276
discreteUpdates$1 @ react-dom.development.js:22413
discreteUpdates @ react-dom.development.js:3756
dispatchDiscreteEvent @ react-dom.development.js:5889

The core error message is:

index.js:14 Uncaught TypeError: Cannot read properties of undefined (reading 'setState')
react-dom.development.js:4091 Uncaught TypeError: Cannot read properties of undefined (reading 'setState')

The code is:

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";

class Square extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: null,
      sname: props.value,
    };
  }
  onClickMe() {
    this.setState({ value: "X" });
    console.log("on click " + this.state.sname);
  }
  render() {
    return (
      <button className="square" onClick={this.onClickMe}>
        {this.state.value}
      </button>
    );
  }
}

class Board extends React.Component {
  renderSquare(i) {
    return <Square value={i} />;
  }

  render() {
    const status = "Next player: X";

    return (
      <div>
        <div className="status">{status}</div>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
        </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
        </div>
        <div className="board-row">
          {this.renderSquare(6)}
          {this.renderSquare(7)}
          {this.renderSquare(8)}
        </div>
      </div>
    );
  }
}

class Game extends React.Component {
  render() {
    return (
      <div className="game">
        <div className="game-board">
          <Board />
        </div>
        <div className="game-info">
          <div>{/* status */}</div>
          <ol>{/* TODO */}</ol>
        </div>
      </div>
    );
  }
}

// ========================================

ReactDOM.render(<Game />, document.getElementById("root"));

You can see that I am just following this tutorial to make a simple react game. I created a component named ‘Square’ to represent a square block of the game, it should show ‘X’ when user click the square. Here is the component code:

class Square extends React.Component {
  constructor(props) { //initialize the Square
    super(props);
    this.state = {
      value: null,
      sname: props.value,
    };
  }
  onClickMe() { //function to change the state of this component
    this.setState({ value: "X" });
    console.log("on click " + this.state.sname);
  }
  render() { //when user click this component, it should call the onClickMe function
    return (
      <button className="square" onClick={this.onClickMe}>
        {this.state.value} 
      </button>
    );
  }
}

The line cause the problem is:

this.setState({ value: "X" }); 

2. The environment

  • The npm/node version info:
➜  ~ npm version
{
  npm: '8.1.2',
  node: '17.1.0',
  v8: '9.5.172.25-node.13',
  uv: '1.42.0',
  zlib: '1.2.11',
  brotli: '1.0.9',
  ares: '1.18.1',
  modules: '102',
  nghttp2: '1.45.1',
  napi: '8',
  llhttp: '6.0.4',
  openssl: '3.0.0+quic',
  cldr: '39.0',
  icu: '69.1',
  tz: '2021a',
  unicode: '13.0',
  ngtcp2: '0.1.0-DEV',
  nghttp3: '0.1.0-DEV'
}

3. The solution

3.1 Why did this error happen?

As the above code shown, we passed function this.onClickMe to onClick of the Button, and call this.setState() inside the function onClickMe, but it seems that we can not access the component’s state from inside our function. After searching, I found a valuable explanation:

Until arrow functions, every new function defined its own this value […]. This proved to be annoying with an object-oriented style of programming.

Arrow functions capture the this value of the enclosing context […]

What we want to achieve is to access the component’s state within our own function, just as follows:

  onClickMe() { //function to change the state of this component
    this.setState({ value: "X" });
    console.log("on click " + this.state.sname);
  }

The above function is inside a react component, and it wants to access the component state and change its state. How to achieve this ?

3.2 Solution #1

As the above explanation, we can use arrow function instead of normal function to access the state, because:

Arrow functions capture the this value of the enclosing context

So we change the code of the react component as follows:

class Square extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: null,
      sname: props.value,
    };
    //define an Arrow function to reference the old function
    this.onClickMeRef = () => { 
      this.onClickMe();         
    };
  }
  onClickMe() {
    this.setState({ value: "X" });
    console.log("on click " + this.state.sname);
  }
  render() { // change the onclick to use the arrow function reference.
    return (
      <button className="square" onClick={this.onClickMeRef}> 
        {this.state.value}
      </button>
    );
  }
}

The first change is to define a arrow function and reference the old function, we define it inside the constructor of the component:

    //define an Arrow function to reference the old function
    this.onClickMeRef = () => { 
      this.onClickMe();         
    };

Then we can use this reference insdie the render() function of the component:

render() { // change the onclick to use the arrow function reference.
    return (
      <button className="square" onClick={this.onClickMeRef}> 
        {this.state.value}
      </button>
    );
  }

Instead of using the normal function this.onClickMe, we use this.onClickMeRef, which would capture the this context and make it possible to access the component’s state inside that function.

3.3 Solution #2

If you think solution #1 is too complicated, we can make it more compact by composing all the lines of the normal function and arrow function into one function:

class Square extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: null,
      sname: props.value,
    };
    this.onClickMeRef = () => { //add this
      this.setState({ value: "X" });
      console.log("on click " + this.state.sname);
    };
  }
  render() {
    return (
      <button className="square" onClick={this.onClickMeRef}> //change this
        {this.state.value}
      </button>
    );
  }
}

Comparing to solution #1, solution #2 deletes the normal function clickMe(), and move all lines into this.onClickMeRef arrow function.

3.4 Solution #3

According to react document about how to bind a function to a component instance, we can just use the bind() function to tell react to transfer the this context to the function, just as follows:

class Square extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: null,
      sname: props.value,
    };
    this.onClickMe = this.onClickMe.bind(this); //add this
  }
  onClickMe() {
    this.setState({ value: "X" });
    console.log("on click " + this.state.sname);
  }
  render() {
    return (
      <button className="square" onClick={this.onClickMe}>
        {this.state.value}
      </button>
    );
  }
}

We just add one line to the original code:

    this.onClickMe = this.onClickMe.bind(this); //add this

We should notice that we must capture the return value of the function bind, we can use it normally in the render function.

4. Summary

In this post, I demonstrated how to solve the ` Cannot read properties of undefined reading setState when using setState inside a normal function inside a react component, we should pay attention to how to passing a function to react component, the solution to this problem is to bind this` to the function or use arrow function . That’s it, thanks for your reading.