Unlike the state
from classes, you are not limited to a single object. With useState
, you can create separate values for each case like this:
const [name, setName] = useState('John');
const [email, setEmail] = useState('john@example.com');
const [age, setAge] = useState(25);
But, I found this very dirty and prefer using only one useState
initialized with an object:
const [user, setUser] = useState(
{ name: 'John', email: 'john@example.com', age: 25 }
);
I understand that there is no strict limit and useState
can store objects, but in class components, when you update state using this.setState
, it is merged with the old one. With useState
, the update function replaces the old state with a new one (there is no merging).
I frequently implemented like below:
setUser(prevStat => ({
...prevState,
name: 'Daniel'
}));
If I'm correct, updating an object state will re-render all fields of the object state, which supposedly will be more expensive for the app?
You could do that, but it's not what the official React docs do or recommend. It's still a working and valid approach.
If you find yourself using a state object because there's a lot of state, or managing interactions gets complicated, it might be a good time to consider using a reducer with useReducer
. You can move the boilerplate and logic out of the immediate call-site and better retain overview over the relationship between properties of you state object. With a reducer you‘d just write dispatch({ type: "SET_NAME", name: "Daniel" })
.
Having all the code that modifies your state in one place (the reducer) allows you to better reason over actions such as resetting the state.
The dispatch function returned by useReducer
is also stable and well suited to be passed to child components, making it easier to refactor your component into smaller ones.