Use Context Api and Immer to Manage the state of your React app
Tags: react, javascript, node, tutorial
Published: 2021-09-17 16:46:17

## Overview

In the past I've written two articles in which I explain how we can use immer.js together with [zustand](https://dev.to/franciscomendes10866/zustand-and-immer-with-react-5ajh) and the [useState](https://dev.to/franciscomendes10866/use-immer-to-better-handle-the-state-of-your-react-app-489p) hook.

But I am fully aware that not everyone uses community-created state managers or using component state is not enough. That's why many people use the Context Api, it comes with React, it's light, fast and very advanced.

One of the advantages of Context Api is that it is immensely flexible, we can have several implementations of it, but in today's example I will use reducers (which I believe is the most used approach).

In this article I won't explain in depth how immer.js actually works because it's a topic in itself, however I recommend reading [this](https://itnext.io/why-concept-of-immutability-is-so-damn-important-for-a-beginner-front-end-developer-8da85b565c8e) article which explains the concept very well.

## Today's example

Today I will take an approach similar to the past, ie at the end of the article I will share with you the github repository so that you can test it more easily. 

But talking about the idea now, I'll show you an example of a reducer with a very typical approach, you must have seen it in courses or even at work. Then I'll show you how to get exactly the same results using immer.js.

# Let's code

The application that you will have access to the github repository looks like this:

![example app with some data](https://res.cloudinary.com/dj5iihhqv/image/upload/v1631894746/Screenshot_2021-09-17_at_17.04.05-min_rk0uwn.png)

In our application we can add a book, update it and remove it.

![adding, updating and deleting a book](https://res.cloudinary.com/dj5iihhqv/image/upload/v1631895037/Kapture_2021-09-17_at_17.09.19-min_ji71ti.gif)

Our reducer code is as follows:

```js
// @/src/store/reducers/books.js

 export default (state, { payload, type }) => {
   switch (type) {
     case "ADD_BOOK":
       return {
         ...state,
         books: {
         ...state.books,
         list: [...state.books.list, payload],
         },
       };
     case "REMOVE_BOOK":
       return {
         ...state,
         books: {
         ...state.books,
         list: state.books.list.filter((book) => book.id !== payload),
         },
       };
     case "UPDATE_BOOK":
       return {
         ...state,
         books: {
         ...state.books,
         list: state.books.list.map((book) => {
             if (book.id === payload.id) {
               return payload;
             }
             return book;
           }),
         },
       };
     default:
       return state;
   }
 };
```

I believe that practically everyone has seen reducers similar to this one at least once in our lives. I want to say right away that this code isn't incorrect when written this way, it's fully functional, and it's the most popular approach I know.

However this approach is not the most friendly for beginners or people who are not used to working with JavaScript. I say this because at some point, the way we learned to manipulate data structures like objects and arrays is using methods.

And that's exactly why we're going to take into account the logic we have in the code above and we're going to use immer.js now. First let's clean our reducer, like this:

```js
// @/src/store/reducers/books.js

 export default (state, { payload, type }) => {
   switch (type) {
     case "ADD_BOOK":
       return;
     case "REMOVE_BOOK":
       return;
     case "UPDATE_BOOK":
       return;
     default:
       return state;
   }
 };
```

Let's start working on `ADD_BOOK`, in our return we will use the `produce()` function from immer.js which will have two arguments. The first argument will be our **state** and the second will be a callback with our state's **draft**.

Then to add a new book to our list of books we just need to use the `push()` method and we pass the book with a single argument.

```js
// @/src/store/reducers/books.js
import produce from "immer";

export default (state, { payload, type }) => {
  switch (type) {
    case "ADD_BOOK":
      return produce(state, (draft) => {
        draft.books.list.push({ ...payload });
      });
    case "REMOVE_BOOK":
      return;
    case "UPDATE_BOOK":
      return;
    default:
      return state;
  }
};
```

Now in our `REMOVE_BOOK` we will do something similar but this time we will remove a book, first we will need to know the book index with id similar to the payload, using the `findIndex()` method. After we get the book's index, we will remove it from the array, using the `splice()` method.

```js
// @/src/store/reducers/books.js
import produce from "immer";

export default (state, { payload, type }) => {
  switch (type) {
    case "ADD_BOOK":
      return produce(state, (draft) => {
        draft.books.list.push({ ...payload });
      });
    case "REMOVE_BOOK":
      return produce(state, (draft) => {
        const bookIndex = draft.books.list.findIndex(
          (book) => book.id === payload
        );
        draft.books.list.splice(bookIndex, 1);
      });
    case "UPDATE_BOOK":
      return;
    default:
      return state;
  }
};
```

Finally in our `UPDATE_BOOK`, we will need to find the book with the id equal to the payload id, using the `find()` method. Once we have our book (which this time is an object) let's update each of its properties, like this:

```js
// @/src/store/reducers/books.js
import produce from "immer";

export default (state, { payload, type }) => {
  switch (type) {
    case "ADD_BOOK":
      return produce(state, (draft) => {
        draft.books.list.push({ ...payload });
      });
    case "REMOVE_BOOK":
      return produce(state, (draft) => {
        const bookIndex = draft.books.list.findIndex(
          (book) => book.id === payload
        );
        draft.books.list.splice(bookIndex, 1);
      });
    case "UPDATE_BOOK":
      return produce(state, (draft) => {
        const book = draft.books.list.find((book) => book.id === payload.id);
        book.title = payload.title;
        book.author = payload.author;
      });
    default:
      return state;
  }
};
```

If you go to test the application you will notice that everything has the same behavior, but this time we have much less code in our reducer, it is easier to read and it is immensely intuitive.

As promised, if you want to access the github repository to test the application click [here](https://github.com/FranciscoMendes10866/soav).

## Conclusion

As always, I hope you found it interesting. If you noticed any errors in this article, please mention them in the comments. 🧑🏻‍💻

Hope you have a great day! 😈