Originally posted on my personal blog debugger.io
If you are a react developer and using a class component, you probably faced this error at least once:
TypeError: this.setState is not a function
TL;DR - If you are not in the mode for reading or you just want the bottom line, then here it is
Prerequisite -
- We are going to mention the
this
context quite a lot here, if you are not exactly sure on howthis
works, i strongly advice reading JavaScript - The "this" key word in depth first. - We are also going to touch a little bit on the prototype chain subject, if you are not exactly sure how it works or not sure how classes works under the hood, i strongly advice reading JavaScript - The prototype chain in depth.
The problem
In order to understand what are the possible solutions, lets first understand what is the exact issue here.
Consider this code block:
class App extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
countUp() {
this.setState(currentState => {
return { count: currentState.count + 1 };
});
}
render() {
const { count } = this.state;
return (
<div>
<button onClick={this.countUp}>{count}</button>
</div>
);
}
}
We have a component that manage a state with a counter
property, we have a handler that is attach to the onClick
of a <button>
which will invoke the React.Component
's setState
method.
unfortunately, this won't work well. When the user click the button we will get an error:
TypeError: this.setState is not a function
We do use the extends React.Component
which means we get access to all React.Component
methods via this
. So how come we can't invoke this.setState
.
The real problem here, is that we "lost" the context of this
inside that handler, or maybe not lost but its not pointing to where we think it should point.
What is the context of this
in our case then?
Lets revisit our flow-chart from the JavaScript - The "this" key word in depth article:
Although there is no "event handlers" flow, we can place them under the "dot notation" or "object's member".
You can look at event handlers that are attached to DOM elements as if the function is a method inside the element’s object, in our case the button
object. We can look at it as if we did button.click()
or even button.countUp(). Note that this is not exactly whats going on under the hood, but this visualization of the invocation of the handler can help us with the formation of our “mental model” regarding the setting of this
. You can read more about it on the MDN.
So what is this
pointing to in our case? Lets walk through the flow:
- Is countUp an arrow function? - No.
- Was countUp called with new? - No.
- Was countUp called with call / apply / bind? - No.
- Was countUp called as an object method? - Yes (sort of), in our case the actual
button
is left to the dot, hencethis
is pointing to thebutton
element.
This is why we have an error, because the button
element does not have any setState
method on it.
Possible solutions
#1 bind
One possible solution is to use bind
and return a new function with an explicit this
reference:
class App extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
countUp() {
this.setState(currentState => {
return { count: currentState.count + 1 };
});
}
render() {
const { count } = this.state;
return (
<div>
<button onClick={this.countUp.bind(this)}>{count}</button>
</div>
);
}
}
This works great and we don't get any errors, although we are creating and passing a new function on each render cycle which may have performance implications (or might not).
We can use bind
in the constructor which will run only once for the entire life-time of the component.
class App extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.countUp = this.countUp.bind(this);
}
countUp() {
this.setState(currentState => {
return { count: currentState.count + 1 };
});
}
render() {
const { count } = this.state;
return (
<div>
<button onClick={this.countUp}>{count}</button>
</div>
);
}
}
This way, we are "overriding" the class method with an instance method, meaning we are not using the countUp
method attached to the App.prototype
but creating a method directly on the instance returned by App
.
If you are not sure you fully understand how the prototype chain works under the hood, or not sure how classes works under the hood, i strongly recommend reading the JavaScript - The prototype chain in depth article
So why is using bind
works for us? Lets walk through the flow again:
- Is countUp an arrow function? - No.
- Was countUp called with new? - No.
- Was countUp called with call / apply / bind? - Yes.
Meaning, our this
will reference whatever we pass to bind
, which is the class instance.
#2 Arrow function
Instead of manually dealing with the this
reference and passing it via bind
, we can let the language / engine do it for us.
When using arrow functions, the engine will not "mutate" the this
reference and will leave it as is, meaning whatever the this
is pointing to at the wrapping execution context.
class App extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
countUp() {
this.setState(currentState => {
return { count: currentState.count + 1 };
});
}
render() {
const { count } = this.state;
return (
<div>
<button onClick={() => this.countUp()}>{count}</button>
</div>
);
}
}
We are passing an inline arrow function and invoking this.countUp
, this way the engine will not "mutate" our this
reference thus it won't point to the button
element, our function is called with a dot notation.
So lets walk through the flow again:
- Is countUp an arrow function? - No.
- Was countUp called with new? - No.
- Was countUp called with call / apply / bind? - No.
- Was countUp called as an object method? - Yes,
this
is the object left to the dot - The auto created object insideApp
in this case (the instance).
While this works great, we are again passing a new function on each render cycle, although it won't create any issues most of the time, you might want to create this function once. We can do that with class fields - Note that at the time this article was written, class fields are a proposal on stage 3.
class App extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
countUp = () => {
this.setState(currentState => {
return { count: currentState.count + 1 };
});
}
render() {
const { count } = this.state;
return (
<div>
<button onClick={this.countUp}>{count}</button>
</div>
);
}
}
And if we are using class fields, why not remove the constructor and just declare the state
as a class field?
class App extends React.Component {
state = { count: 0 };
countUp = () => {
this.setState(currentState => {
return { count: currentState.count + 1 };
});
};
render() {
const { count } = this.state;
return (
<div>
<button onClick={this.countUp}>{count}</button>
</div>
);
}
}
Now back to our this
, why does it work with arrow functions? Lets walk through the flow again:
- Is countUp an arrow function? - Yes. So whatever the
this
is in the wrapping context, which is the class instance.
Wrapping up
Make sure you don't "lose" the context of this
in your handlers, either explicit pass it with bind
(inline or override in constructor) or use an arrow function (inline or class field) that won't mutate and change the reference of this
when it gets called.
I hope it was informative and helpful, if you have any further clarifications or corrections, feel free to comment or DM me on twitter (@sag1v). 🤓
For more articles you can visit debuggr.io
Top comments (1)
Thanks! And i totally agree