Let's start with this component which allows us to change the user's bio:
importReact,{useState}from"react";functionUserCardEditor(){const[state,setState]=useState({id:14,email:"example@domain.com",profile:{name:"Horus",bio:"Lorem ipsum dolor sit amet..."}});functionchangeBio(){constnewBio=prompt("New bio",state.profile.bio);setState(current=>({...current,profile:{...current.profile,bio:newBio}}));}return(<div>
Name: {state.profile.name}<p>{state.profile.bio}</p><buttononClick={changeBio}>Change Bio</button></div>);}exportdefaultUserCardEditor;
A few things to care about:
We're saving all the state inside the useState hook. To update it we need to call setState.
The only thing we're trying to modify here is the user's bio. Notice how it's nested inside the profile object.
React expects you to replace the state with a new one, to do that you must create a new object and pass it to the setState function!
Knowing that, it's simple to understand the reason behind doing this to update the state, right?
Yay! But... the view didn't update! Why? Well... remember that React expects you to use a NEW object, and that's not a new object, it's still the old one, you simply mutated one of it's properties.
Then... should we just stick to the long and noisy way that uses the spread operator?
You could, but... Someone already solved this problem!
immer and use-immer
Ever heard of immer? You may have heard of this library if you've been playing with Redux! If you didn't, let's take a look into how we can use Immer with React!
First, let's install it:
$ npm install immer use-immer
Now add this import in one of your files:
import{useImmer}from'use-immer';
We were editing the UserCardEditor component right? Let's replace the useState with useImmer:
For now, it's the same as before... But Immer actually allows us to mutate the data in order to update it! We can now replace our setState call with this:
setState(draft=>{draft.profile.bio=newBio;});
Because we're using Immer, the library will work behind the scenes to create a copy of the object and apply the same modifications that we do to the draft object. With this, we can use mutation to update our React state!
Here's the final code:
importReact,{useState}from"react";import{useImmer}from"use-immer";functionUserCardEditor(){const[state,setState]=useImmer({id:14,email:"example@domain.com",profile:{name:"Horus",bio:"Lorem ipsum dolor sit amet..."}});functionchangeBio(){constnewBio=prompt("New bio",state.profile.bio);setState(draft=>{draft.profile.bio=newBio;});}return(<div>
Name: {state.profile.name}<p>{state.profile.bio}</p><buttononClick={changeBio}>Change Bio</button></div>);}exportdefaultUserCardEditor;
The use-immer library also has a replacement for useReducer, but we won't be covering it here, I recommend you to go to their repo and check out the examples:
A hook to use immer as a React hook to manipulate state.
Installation
npm install immer use-immer
API
useImmer
useImmer(initialState) is very similar to useState
The function returns a tuple, the first value of the tuple is the current state, the second is the updater function
which accepts an immer producer function or a value as argument.
Managing state with immer producer function
When passing a function to the updater, the draft argument can be mutated freely, until the producer ends and the changes will be made immutable and become the next state.