Immer JS - Technological watch

Learn what is Immer JS in less than 5 minutes !
Wednesday, June 15, 2022

Problematic

In JavaScript, when we use arrays, we can be confronted with this problem:

const items = [1, 8, 5, 3];
const sorted = items.sort();
if (items === sorted) {
// this condition is true !
// But why ?
}

Here, the sort() method change the items variable instead of creating a new array.

We say that sort() makes a mutation.

To avoid this, we have to create a copy of the items array:

Here is an example:

const items = [1, 3, 5, 8];
const newItems = [...items]; // Create a copy before sorting
newItems.sort();
// Here, items !== newItems

But in some cases, using immutable data in JavaScript can be hard. Let’s pick an easy example:

const todos = [
{ title: "Do technological watch", completed: true },
{ title: "Clean my room", completed: false },
{ title: "Prepare a gift for my friends", completed: false },
];

Imagine you have a to-do list, and you want to create a function which toggles one to-do (without changing or mutating).

You have to do something like this:

const toggleTodo = (todos, index) => {
return todos.map((todo, i) =>
i === index
? {
...todo,
completed: !todo.completed,
}
: todo
);
};
// -----
toggleTodo(todos, 1);
// returns:
[
{ title: "Do technological watch", completed: true },
{ title: "Clean my room", completed: true },
{ title: "Prepare a gift for my friends", completed: false },
];
// And "todos" isn't modified !

Look at the syntax. It isn’t natural, right ? And here, we have a simple case (we only want to toggle one to-do !).

Now imagine we have a more complex case ! 😱

A solution: ImmerJS

ImmerJS is a JavaScript library used to simplify the handling of immutable data structures.

You can find more information by following this link: https://immerjs.github.io/immer/. To import it in your projects, you can do:

Terminal window
npm install immer
# 3KB gzipped

To play with a state, you just have to use a produce function which receive the state, and a draft in parameter.

The draft is the new version of the state. You can do any modifications you want as imperative code.

Here’s an example:

import produce from "immer";
const baseState = [
{
title: "Learn TypeScript",
completed: true,
},
{
title: "Try Immer",
completed: false,
},
];
const nextState = produce(baseState, (draftState) => {
draftState.push({ title: "Tweet about it" });
draftState[1].done = true;
});
// Here, baseState !== nextState

Immer returns the new immutable state. The big advantage is that the library offers a simple and readable way to make complex tasks.

Work with React JS

Immer works great with ReactJS.

Immer provided some hooks.

Let’s take a look with the useImmer hook:

import React, { useCallback } from "react";
import { useImmer } from "use-immer";
const TodoList = () => {
const [todos, setTodos] = useImmer([
{
id: "React",
title: "Learn React",
done: true
},
{
id: "Immer",
title: "Try Immer",
done: false
}
]);
const handleToggle = useCallback((id) => {
setTodos((draft) => {
const todo = draft.find((todo) => todo.id === id);
todo.done = !todo.done;
});
}, []);
const handleAdd = useCallback(() => {
setTodos((draft) => {
draft.push({
id: "todo_" + Math.random(),
title: "A new todo",
done: false
});
});
}, []);
// etc

Easier right ?

Other supports

  • ImmerJS supports more complex data structures like Map/Set or Classes.

  • It offers other easy APIs and even a patch system !

  • Finally, it fully supports Typescript, so no any types problems anymore !

My opinion

ImmerJS is a great small library to avoid the mutable state problem.

The final code is cleaner, and no needs to think about hacks.

With ImmerJS, it is easier to solve some ReactJS / functional cases.


Recommended articles