Use React's useState and useReducer without Worrying about Immutability
Tags: react, codenewbie, tutorial, javascript
Published: 2020-08-03 19:39:33

Struggling with immutability? Finding a lot of spread operators in your codebase? This guide is for you!

TL;DR: Check out the https://github.com/immerjs/use-immer library, it's awesome!

---

Let's start with this component which allows us to change the user's bio:

```jsx
import React, { useState } from "react";

function UserCardEditor() {
  const [state, setState] = useState({
    id: 14,
    email: "example@domain.com",
    profile: {
      name: "Horus",
      bio: "Lorem ipsum dolor sit amet..."
    }
  });

  function changeBio() {
    const newBio = 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>
      <button onClick={changeBio}>Change Bio</button>
    </div>
  );
}

export default UserCardEditor;

```

{% codesandbox use-immer-example-1-ql9br module=/src/UserCardEditor.js %}

**A few things to care about:**
1. We're saving all the state inside the `useState` hook. To update it we need to call `setState`.
2. The only thing we're trying to modify here is the user's bio. Notice how it's nested inside the profile object.
3. 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?  

```jsx
...

setState(current => ({
  ...current,
  profile: {
     ...current.profile,
     bio: newBio
  }
}));

...
```

I don't blame you if you don't think it's simple, because it's not. All these lines of code can be represented with this if you're using mutation:

```jsx
setState(current => { 
  current.profile.bio = newBio;
});
```

You see? A single line instead of cloning the object using the spread operator multiple times. That's simple!

And... illegal. React expects you to return something from that function, maybe we can just return the same object?

```jsx
setState(current => { 
  current.profile.bio = newBio;
  return current;
});
```

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:

```bash
$ npm install immer use-immer
```

Now add this import in one of your files:

```js
import { useImmer } from 'use-immer';
```

We were editing the UserCardEditor component right? Let's replace the `useState` with `useImmer`:

```diff
- const [state, setState] = useState({
+ const [state, setState] = useImmer({
   id: 14,
   email: "example@domain.com",
   profile: {
     name: "Horus",
     bio: "Lorem ipsum dolor sit amet..."
   }
 });
```

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:

```jsx
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:

```jsx
import React, { useState } from "react";
import { useImmer } from "use-immer";

function UserCardEditor() {
  const [state, setState] = useImmer({
    id: 14,
    email: "example@domain.com",
    profile: {
      name: "Horus",
      bio: "Lorem ipsum dolor sit amet..."
    }
  });

  function changeBio() {
    const newBio = prompt("New bio", state.profile.bio);

    setState(draft => {
      draft.profile.bio = newBio;
    });
  }

  return (
    <div>
      Name: {state.profile.name}
      <p>{state.profile.bio}</p>
      <button onClick={changeBio}>Change Bio</button>
    </div>
  );
}

export default UserCardEditor;

```

{% codesandbox use-immer-example-2-h3xwe module=/src/UserCardEditor.js %}

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:

{% github immerjs/use-immer %}

---

That's all! Follow me on [Twitter](https://twitter.com) if you want to know more about my future projects, posts or whatever I came up with!