DEV Community

Cover image for An Introduction To Immer in React
Ashish Prajapati
Ashish Prajapati

Posted on • Edited on

An Introduction To Immer in React

Introduction

When we deal with updating any nested or too deep property within an object (nested or flat level), it is possible that you might have run into some weird issues:-

  • State does not get properly updated.
  • Updating it affects somewhere else.
  • Or it completely destroys the application.
  • Always creating shallow copy and updating the state in immutable way.

We often find ourself doing these to fix it:-

// parsing from json
JSON.parse(JSON.stringify(BigNestedObject))

// lodash clone
lodash.clone(BigNestedObject, true)

// shallow copy, most people do not understand deep copy
const newObject = {...BigNestedObject}

// deep copy on every update. But very expensive
structuredClone(BigNestedObject)
Enter fullscreen mode Exit fullscreen mode

So to deal with all these sort of problems, we can use Immer and make our life much better once for all. Let's understand more about it.


Table of content


What is Immer?

Immer is a developer friendly and performant package, which helps to manage immutable states in our application with ease. Updating the state in mutable way but it's immutable. Read offical Docs here (https://immerjs.github.io/immer/)


What does Immer brings to the table?

  • Immer provides a way deal with Immutable data structures which allow for efficient change detection:-
    • if the reference to an object didn't change, the object itself did not change.
  • It makes cloning relatively cheap:-
    • Unchanged parts of a data tree don't get copied and are shared in memory with older versions of the same state.
  • We do not need to spread or copy the object again and again. No deep/shallow copy problem anymore.
  • We do not need to return anything from the functions provided by Immer. It's get automatic reflected and retured by the function.

Installation

npm i immer
Enter fullscreen mode Exit fullscreen mode

Simple Example with produce function of Immer

  • Managing simple list with produce function. It takes two argument, first one is initialState and second one is producer function where it recieves one argument draft a proxy of the initialState, which can mutated safely.
import {produce} from "immer"

const animeList= [
    {
        title: "One punch man",
        done: true
    },
    {
        title: "Hunter X Hunter",
        done: false
    }
]

// adding new item into the list
const nextState = produce(animeList, draftState => {
    draftState.push({title: "Hajime No Ippo"})
    draftState[1].done = true
})

console.log(nextState === animeList) // false
console.log(nextState[0] === animeList[0]) // true
console.log(nextState[1] === animeList[1]) // false

Enter fullscreen mode Exit fullscreen mode

Using Immer in React

  • We can use just produce function in our React app for updating/modifying the states, but we have more powerful tool provided by Immer, and it is useImmer hook. useImmer is just as same implementation like useState and it's syntax is same as to it, behind the scene it creates proxy and manages everything for us.

Installing use-immer library

npm i use-immer
Enter fullscreen mode Exit fullscreen mode

Below example is managing the animeList with immer. Where we are rendering the list, can add new item into the list, and can update the watch status of each item and rendering the whole state-tree in json.

import { useImmer } from 'use-immer';
import { useState } from 'react';

export default function AnimeList() {
  const [list, updateList] = useImmer([
    { id: 1, title: "One Punch Man", done: true },
    { id: 2, title: "Hunter X Hunter", done: false }
  ]);
  const [input, setInput] = useState('');

  const toggle = (id) => {
    updateList(draft => {
      const item = draft.find(a => a.id === id);
      if (item) item.done = !item.done;
    });
  };

  const add = () => {
    if (input.trim()) {
      updateList(draft => {
        draft.push({ id: Date.now(), title: input.trim(), done: false });
      });
      setInput('');
    }
  };

  return (
    <div className="max-w-md mx-auto mt-8 p-6 bg-white rounded shadow">
      <h1 className="text-xl font-bold mb-4">Anime List</h1>

      <div className="flex gap-2 mb-4">
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          onKeyPress={e => e.key === 'Enter' && add()}
          placeholder="Add anime..."
          className="flex-1 px-3 py-2 border rounded"
        />
        <button onClick={add} className="px-4 py-2 bg-blue-500 text-white rounded">
          Add
        </button>
      </div>

      <div className="space-y-2">
        {list.map(anime => (
          <div key={anime.id} className="flex items-center gap-2 p-2 border rounded">
            <input
              type="checkbox"
              checked={anime.done}
              onChange={() => toggle(anime.id)}
            />
            <span className={anime.done ? 'line-through text-gray-400' : ''}>
              {anime.title}
            </span>
          </div>
        ))}
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

A brief comparison

// ❌ ❌ ❌ ❌ ❌  useState in nutshell
const [state, setState] = useState([])

setState((oldState) => {
  const clonedState = [...oldState]    // ❌ shallow cloning
  clonedState.push(someData)
  return clonedState   // ❌ returning cloned state
})


// ✅ ✅ ✅ ✅ ✅   useImmer
const [state, setState] = useImmer([])

setState((oldState) => {
  clonedState.push(someData)   //✅ just pushing new data
})


Enter fullscreen mode Exit fullscreen mode

Tips to better utilize Immer

  • Do not use immer where your state is not nested object or it's just flat level or dealing with only primitive values.
  • Using too much Immer in application can make it very memory heavy, as proxies takes up extra space.
  • We can use produce function in our reducers to manage the state.
  • Do not use it if your build size is constraint.
  • We can also use immer for managing Map object states. (https://immerjs.github.io/immer/map-set)

If learned something by reading the blog, please do like and share. 😄

Top comments (0)