Use Immer with useState Hook to Handle the State of your React app

In the article I published earlier, I had been using Zustand together with Immer to handle objects and arrays in a more intuitive way. However, I am fully aware that many people prefer to use the local state of their components and only when they have no alternative do they opt for a global state solution.

Exactly for this reason I decided to write this article, today we are going to use the useState() hook along with Immer.

As I did in the previous article, I will give the final application code at the end of the article and what I will explain today is just related to how to implement the Immer in the Set state function of our component.

The idea of the application is to list a list of manga they like, for that we'll have to create, delete and edit. The initial state of the list will already contain about three elements.

Let's code

Imagine that your application looks like this:

And the code looks like this:

// @src/App.jsx

import React, { useState } from "react";

const App = () => {
  const [name, setName] = useState("");
  const [list, setList] = useState([
    {
      id: Math.floor(Math.random() * 100),
      title: "Cheongchun Blossom",
    },
    {
      id: Math.floor(Math.random() * 100),
      title: "Koe no Katachi",
    },
    {
      id: Math.floor(Math.random() * 100),
      title: "By Spring",
    },
  ]);
  const [isEdit, setIsEdit] = useState(false);
  const [update, setUpdate] = useState({
    id: null,
    title: "",
  });
  const handleOnSubmit = (e) => {
    e.preventDefault();
    setList(
      // Logic goes here
    );
    setName("");
  };
  const handleDelete = (id) => {
    setList(
      // Logic goes here
    );
  };
  const handleOnPatch = () => {
    setList(
      // Logic goes here
    );
    setName("");
    setIsEdit(false);
    setUpdate({
      id: null,
      title: "",
    });
  };
  const handleIsEdit = (manga) => {
    setIsEdit(true);
    setUpdate(manga);
  };
  return (
    // Hidden for simplicity
  );
};

export default App;

As you may have noticed, the page is already finished and we just work on the handlers of the functionality of creating, deleting and editing.

For that, I will focus on each one of them individually. Let's eat by working on handleOnSubmit.

const handleOnSubmit = (e) => {
    e.preventDefault();
    setList(
      // Logic goes here
    );
  };

Normally, what we would do is use spread operatorions to add a new element to the state, like this:

const handleOnSubmit = (e) => {
    e.preventDefault();
    setList([...list, { id: Math.floor(Math.random() * 100), title: name }]);
  };

What we would be doing was creating a new array, first adding the data we already have in our state and only then adding the new element.

However, we are going to use Immer to handle this entire process. What the Immer will do is create a copy of our state, which is called draft, to which we will make our changes and who will be in charge of making the necessary changes to the state will be the Immer.

First let's import the Immer into our project and let's use the produce() function:

import produce from "immer";

// Hidden for simplicity

const handleOnSubmit = (e) => {
    e.preventDefault();
    setList(
      produce(() => {
        // Logic goes here
      })
    );
  };

In the produce() function, we'll get our draft and then we'll add an element to our state, like this:

const handleOnSubmit = (e) => {
    e.preventDefault();
    setList(
      produce((draft) => {
        draft.push({
          id: Math.floor(Math.random() * 100),
          title: name,
        });
      })
    );
  };

Now if we are going to test our application, we should already be able to add a new element to the list.

Now we can implement the removal of an element from the list:

const handleDelete = (id) => {
    setList(
      produce((draft) => {
        // Logic goes here
      })
    );
  };

As you may have noticed, when using Immer, we are programming as a vanilla JavaScript, without any kind of paradigms imposed by a library.

With this in mind, let's look for an array element that has the id equal to the id that we're going to pass in the function arguments to get the index value.

const handleDelete = (id) => {
    setList(
      produce((draft) => {
        const i = draft.findIndex((el) => el.id === id);
        // More logic goes here
      })
    );
  };

And then we'll remove it.

const handleDelete = (id) => {
    setList(
      produce((draft) => {
        const i = draft.findIndex((el) => el.id === id);
        draft.splice(i, 1);
      })
    );
  };

Now if we go to our app and click on an element in the list, it will be removed.

Now we only need to update one element of the list:

const handleOnPatch = () => {
    setList(
      produce((draft) => {
        // Logic goes here
      })
    );
  };

First we have to find the array element that has exactly the same id as the update state.

const handleOnPatch = () => {
    setList(
      produce((draft) => {
        const manga = draft.find((el) => el.id === update.id);
        // Logic goes here
      })
    );
  };

Then we'll change the value of the object's title property that was found in the array to the new title.

const handleOnPatch = () => {
    setList(
      produce((draft) => {
        const manga = draft.find((el) => el.id === update.id);
        manga.title = update.title;
      })
    );
  };

The result should look like the following:

As promised, if you want access to the final code of this article's example, click here to access the github repository.

Conclusion

Although it was a small and simple example, I hope that I was clear in explaining things and that I didn't drag something out. I hope this article helps you to improve your productivity when dealing with nested data.

Have a great day! ☺️ ☺️

31