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.