React hooks allow us to create much cleaner code than when using class components (up to 90% cleaner).
We’ll explore the useState
hook today by comparing two versions of the same counter component. One of them is written using the class syntax and the other using hooks.
Counter (Class): https://codepen.io/alveem/pen/VwayxYx
Counter (Hooks): https://codepen.io/alveem/pen/OJNzvqa
Intro
Here’s what the class based counter’s code looks like:
|
|
And here’s what the useState
version looks like:
|
|
The useState
method takes in an initial state value and returns an array where the first element is a reference to the state and the second element is a function to change the state.
Note that we’re destructuring the array to make the code cleaner. It’s the same as the following:
|
|
Asynchronicity
Let’s see whether the asynchronous behavior of state change is affected by hooks.
Class Component
We want to increase our counter by 2
instead of 1
now. Let’s add another this.setState
statement to our increase
method.
|
|
But our counter still only increases by 1
because setState
is asynchronous. Since setState
uses Object.assign
to assign the state updates it ends up doing this:
|
|
The last object with a matching key passed to Object.assign
is used to update the original object’s value.
Let’s try passing a function to setState
instead:
|
|
And now our counter increases by 2
.
Functional Component
Now we want to do the same using useState
. We’ll call setCount
a second time inside our increase
method.
|
|
And the counter only increases by 1
. So this works the same way as when we passed an object to setState
in a class component.
Fortunately, the setCount
function can also has a different form.
|
|
This works now and our counter increases by 2
.
Differences
There are some differences in the behavior of setState
and the setter function returned by useState
(the setCount
function in our example).
Receiving props
The setState
method passes in the props as the second argument to the callback function passed to it.
this.setState((state, props) => console.log(props))
State Update Behavior
As mentioned above, setState
merges objects to update state. Hooks replace the entire state.
Let’s try to limit our counter’s decrease
method so it doesn’t go below 0
.
Class Component (setState)
Here’s what the method would look in the class component.
|
|
Notice that on line 3 we’re not returning anything. The state still updates since React merges the old state with whatever is returned.
Functional Component (useState)
What happens when we do something similar in our functional component?
|
|
When the counter is at 0
and we try to decrease it, the number disappears.
The new state is set to undefined
since whatever we return in callback function passed to setCount
is set as the new state. And if we try to increase the counter we’ll get a NaN
.
Instead, you need to return the entire new state because new values aren’t automatically merged as in setState
.
|
|
And now it works as expected.
If you use an object with multiple values in useState
, a new object needs to be passed in that contains the old key-value pairs along with the new key-value pair(s).
For managing complex state, the Hooks docs recommend using useReducer
.
Further Readings
- How Are Function Components Different From Classes?
- Should I Use Multiple State Variables in
useState
? - React Hooks Docs
- Rules of Hooks (from the docs)
- Don’t call Hooks inside loops, conditions, or nested functions.
- Don’t call Hooks from regular JavaScript functions.