When using class components you can use setState
method to update only part of the state object. React will merge updated value with the previous value of the state object:
//Somewhere in CLASS COMPONENT
//set initial state:
state = {
name: 'Bob',
age: 25,
};
//update name value with setState method:
this.setState({ name: 'Alice' });
//updated state:
state = {
name: 'Alice',
age: 25,
};
Now let's try to do the same thing with useState
hook and function component:
//Somewhere in FUNCTION COMPONENT
//set initial state:
const [state, setState] = useState({ name: 'Bob', age: 25 });
//update name value with setState updater function
setState({ name: 'Alice' });
//updated state:
state = {
name: 'Alice',
};
As you can see we "lost" information about age. setState
didn't just update name property. It returned new state object that was passed to it.
One way to solve this problem is to use setState
with the callback function:
//Somewhere in FUNCTION COMPONENT
//set initial state:
const [state, setState] = useState({ name: 'Bob', age: 25 });
//update name value with setState and callback function
setState(prevState => ({ ...prevState, name: 'Alice' }));
//updated state:
state = {
name: 'Alice',
age: 25,
};
This works perfectly fine. But... if you want to use terse version of this.setState
method in function component (or just have some fun) you can use useReducer
hook to do it.
useReducer like setState method
First let's write some pseudo code:
//Somewhere in FUNCTION COMPONENT
const reducer = (prevState, updatedProperty) => ({
...prevState,
...updatedProperty,
});
const initState = {
name: 'Bob',
age: 25,
};
//initialize state with initState
const [state, setState] = useReducer(reducer, initState);
//update name value (like we do in class component!)
setState({ name: 'Alice' });
//updated state:
state = {
name: 'Alice',
age: 25,
};
This time it worked as expected. We've updated name value and didn't lose age property.
And now full working example:
import React, { useReducer, useEffect } from 'react';
const reducer = (prevState, updatedProperty) => ({
...prevState,
...updatedProperty,
});
const initState = {
name: 'Bob',
age: 25,
isLoading: true,
};
function App() {
const [state, setState] = useReducer(reducer, initState);
const handleOnChange = (e) => setState({ [e.target.name]: e.target.value });
useEffect(() => {
setState({ isLoading: false });
}, []);
const { name, age, isLoading } = state;
return(
<>
{isLoading ? 'Loading...' : (
<>
<input type="text" name="name" value={name} onChange={handleOnChange} />
<input type="text" name="age" value={age} onChange={handleOnChange} />
</>
)}
</>
);
}
And now the most important question:
Do you have to use useReducer
here?
No!
Can you?
Yes! If you like :)
This post has been inspired by Kent C. Dodds' course "Simplify React Apps with React Hooks".
Top comments (13)
Your approach to state handling is incorrect. The power of
useState
comes from the possibility to split state into smaller chunks. So you don't have to store objects at all, like the way you did it. In fact, you can create something like this:With this approach you get more flexibility. So your use case of
useReducer
is incorrect in assumptions. Furthermore, with my suggested approach you get more power when controlling data changes and responding to them withuseEffect
hook.Hi @sunpietro , thank you for your comment!
Firts of all the goal of this post was to show how to mimic
setState
method withuseReducer
hook (mostly for fun as I mentioned in the post).I also wanted to show that one doesn't have to use actions, switch statement, payloads etc. while using
useReducer
hook (like we do with Redux).I totally agree that one of the advantages of hooks is the posibility to split and compose chunks of code the way it is convinent for the programmer. You rarely have to do something. More often you can if you like.
Having said that using
useReducer
makes sense when you want to keep in state variables that are related to each other. E.g. imagine that you have form with dozens of fields and you want to implement reset functionality. UsinguseState
means you have to remember to manually reset every single field one by one:With
useReducer
you can do the same thing like this:The more you have to remember and do manually the bigger possibiity of bug (to say nothing about how tedious it might be).
If you happen to use TypeScript the advantage of the second approach is even bigger (e.g. you'll get an error if you forget to reset some field).
Again the goal of this post was not to show how to use
useState
vsuseReducer
So hopefully my approach wasn't that bad after all? :)
In your example, if you add another property, for example, the 'Middle Name', you have to add a row in either the cases (using multiple set or a setState).
The number of things to remember is the same, so the 'possibility' of a mistake is the same.
@alfredosalzillo yes but having them groupped in one object means: they are connected to each other.
This way I don't have to remember which state variables should be changed. I can take a look at initial
formData
object and see all properties that need to be reset.I totally agree with you. For instance, say you have a form with a few inputs. When you go to submit that form, if you are using
useState
, then you have to create an object with all of the fields in order to submit the data. Sure... no big deal, but when usinguseReducer
, you simply submit yourstate
because it is already an object.Anyone saying that you are "doing it wrong" is, well.... wrong. There is no rule that says you can't use
useReducer
just like you are using it in this example. I actually prefer it and if it works easier for you, then "you are doing it right" (especially since there are no perf hits and nothing wrong syntactically).Both approaches have their use cases.
In a large-scale app it would be simply to cumbersome to explicitly set
useState
for each of the state variables. So I would useuseReducer
in that case.When there is too many state variables in a component, then it might be a sign that a component can be splitted into smaller ones. But it depends on a use case.
I agree with you. It might be the case, so it's good to have options and make informed trade-offs.
Your state might arrive from an external source as a complete object. You may also have a 'table' component which renders and manages the state of the entire table. So complex objects is a state are actually very usual.
To say his approach is "incorrect" is incorrect. It is just that, an approach. It is the method by which he chose to solve a problem. There is nothing in the React docs that says this is "incorrect" and there are no facts that back up that this approach is "incorrect". It's okay to disagree with an approach and prefer another approach, but I would be careful of making factual statements when really you are stating an opinion.
If you look at the React source code, you'll see that
useState
actually usesuseReducer
internally.github.com/facebook/react/blob/720...
If states variables are connected to each other (like in my example with form fields above) I would go with
useReducer
instead ofuseState
.Otherwise I think
useState
is a better option.You might be interested in Hookstate library (hookstate.js.org/) It supercharges React.useState hook to deal with complex, frequently changing states, global states, a lot more..