A Better Way to Handle State with Immer

How to handle React state and complex data structures in an immutable way with Immer

Photo by Clément Hélardot on Unsplash

What is Immer?

Immer is a tiny package that allows you to work with immutable states in a more convenient way.

How Immer Works?

The basic idea with Immer is that all the changes are applied to a temporary draft called Proxy of that currentState. Once all your mutations are completed, Immer will produce the nextState based on the mutations to the draft state.

Under the hood, Immer figures out which parts of the draft have been changed and produces a completely new object that contains the changes.

This is why you can mutate it freely as much as you like!

//Syntax for using Immer 
updatePerson(draft => {
draft.places.city = 'Lagos';
});

Using Produce

The Immer package exposes a default function that does all the work.

produce(currentState, recipe: (draftState) => void): nextState

produce takes a base state, and a recipe that can be used to perform all the desired mutations on the draft that is passed in. The interesting thing about Immer is that the baseState will be untouched, but the nextState will reflect all changes made to draftState.

Inside the recipe, all standard JavaScript APIs can be used on the draft object, including field assignments, delete operations, and mutating array, Map and Set operations like push, pop, splice, set, sort, remove, etc.

Replacing State with Immer

If your state is deeply nested, you might want to consider flattening it. But, if you don’t want to change your state structure, you might prefer a shortcut for nested state structure, which is Immer. But unlike a regular mutation, it doesn’t overwrite the past state!

useState + Immer:

The useState hook assumes any state that is stored inside it is treated as immutable. Deep updates in the state of React components can be greatly simplified by using Immer. The following example shows how to use produce in combination with useState

import React, { useCallback, useState } from "react";
import produce from "immer";
const TodoList = () => {
const [todos, setTodos] = useState([
{
id: "React",
title: "Learn React",
done: true
},
{
id: "Immer",
title: "Try Immer",
done: false
}
]);
const handleToggle = useCallback((id) => {
setTodos(
produce((draft) => {
const todo = draft.find((todo) => todo.id === id);
todo.done = !todo.done;
})
);
}, []);
const handleAdd = useCallback(() => {
setTodos(
produce((draft) => {
draft.push({
id: "todo_" + Math.random(),
title: "A new todo",
done: false
});
})
);
}, []);
return (<div>{* List of Todo */}</div>)
}

useImmer

useImmer(initialState) is very similar to useState. The function returns a tuple, the first value of the tuple is the current state, the second is the updater function, which accepts an Immer producer function or a value as arguments.

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
});
});
}, []);

Using Immer with Reducer

useReducer + Immer

Similarly to useState, useReducer combines neatly with Immer as well, as below example

import React, {useCallback, useReducer} from "react"
import produce from "immer"
const TodoList = () => {
const [todos, dispatch] = useReducer(
produce((draft, action) => {
switch (action.type) {
case "toggle":
const todo = draft.find(todo => todo.id === action.id)
todo.done = !todo.done
break
case "add":
draft.push({
id: action.id,
title: "A new todo",
done: false
})
break
default:
break
}
}),
[
/* initial todos */
]
)
const handleToggle = useCallback(id => {
dispatch({
type: "toggle",
id
})
}, [])
const handleAdd = useCallback(() => {
dispatch({
type: "add",
id: "todo_" + Math.random()
})
}, [])
}

useImmerReducer

import React, { useCallback } from "react";
import { useImmerReducer } from "use-immer";
const TodoList = () => {
const [todos, dispatch] = useImmerReducer(
(draft, action) => {
switch (action.type) {
case "toggle":
const todo = draft.find((todo) => todo.id === action.id);
todo.done = !todo.done;
break;
case "add":
draft.push({
id: action.id,
title: "A new todo",
done: false
});
break;
default:
break;
}
},
[ /* initial todos */ ]
);

Conclusion

Immer is a great way to keep the update handlers concise, especially if there’s nesting in your state, and reduce copying repetitive code. It’s an effective solution for those of you looking to mix useState and useReducer with more complex data structures such as deeply nested arrays.

Unlock 10x development with independent components

Building monolithic apps means all your code is internal and is not useful anywhere else. It just serves this one project. And as you scale to more code and people, development becomes slow and painful as everyone works in one codebase and on the same version.

But what if you build independent components first, and then use them to build any number of projects? You could accelerate and scale modern development 10x.

OSS Tools like Bit offer a powerful developer experience for building independent components and composing modular applications. Many teams start by building their Design Systems or Micro Frontends, through independent components. Give it a try →

An independent product component: watch the auto-generated dependency graph


A Better Way to Handle State with Immer was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Bits and Pieces - Medium and was authored by Nivetha Krishnan

How to handle React state and complex data structures in an immutable way with Immer

Photo by Clément Hélardot on Unsplash

What is Immer?

Immer is a tiny package that allows you to work with immutable states in a more convenient way.

How Immer Works?

The basic idea with Immer is that all the changes are applied to a temporary draft called Proxy of that currentState. Once all your mutations are completed, Immer will produce the nextState based on the mutations to the draft state.

Under the hood, Immer figures out which parts of the draft have been changed and produces a completely new object that contains the changes.

This is why you can mutate it freely as much as you like!

//Syntax for using Immer 
updatePerson(draft => {
draft.places.city = 'Lagos';
});

Using Produce

The Immer package exposes a default function that does all the work.

produce(currentState, recipe: (draftState) => void): nextState

produce takes a base state, and a recipe that can be used to perform all the desired mutations on the draft that is passed in. The interesting thing about Immer is that the baseState will be untouched, but the nextState will reflect all changes made to draftState.

Inside the recipe, all standard JavaScript APIs can be used on the draft object, including field assignments, delete operations, and mutating array, Map and Set operations like push, pop, splice, set, sort, remove, etc.

Replacing State with Immer

If your state is deeply nested, you might want to consider flattening it. But, if you don’t want to change your state structure, you might prefer a shortcut for nested state structure, which is Immer. But unlike a regular mutation, it doesn’t overwrite the past state!

useState + Immer:

The useState hook assumes any state that is stored inside it is treated as immutable. Deep updates in the state of React components can be greatly simplified by using Immer. The following example shows how to use produce in combination with useState

import React, { useCallback, useState } from "react";
import produce from "immer";
const TodoList = () => {
const [todos, setTodos] = useState([
{
id: "React",
title: "Learn React",
done: true
},
{
id: "Immer",
title: "Try Immer",
done: false
}
]);
const handleToggle = useCallback((id) => {
setTodos(
produce((draft) => {
const todo = draft.find((todo) => todo.id === id);
todo.done = !todo.done;
})
);
}, []);
const handleAdd = useCallback(() => {
setTodos(
produce((draft) => {
draft.push({
id: "todo_" + Math.random(),
title: "A new todo",
done: false
});
})
);
}, []);
return (<div>{* List of Todo */}</div>)
}

useImmer

useImmer(initialState) is very similar to useState. The function returns a tuple, the first value of the tuple is the current state, the second is the updater function, which accepts an Immer producer function or a value as arguments.

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
});
});
}, []);

Using Immer with Reducer

useReducer + Immer

Similarly to useState, useReducer combines neatly with Immer as well, as below example

import React, {useCallback, useReducer} from "react"
import produce from "immer"
const TodoList = () => {
const [todos, dispatch] = useReducer(
produce((draft, action) => {
switch (action.type) {
case "toggle":
const todo = draft.find(todo => todo.id === action.id)
todo.done = !todo.done
break
case "add":
draft.push({
id: action.id,
title: "A new todo",
done: false
})
break
default:
break
}
}),
[
/* initial todos */
]
)
const handleToggle = useCallback(id => {
dispatch({
type: "toggle",
id
})
}, [])
const handleAdd = useCallback(() => {
dispatch({
type: "add",
id: "todo_" + Math.random()
})
}, [])
}

useImmerReducer

import React, { useCallback } from "react";
import { useImmerReducer } from "use-immer";
const TodoList = () => {
const [todos, dispatch] = useImmerReducer(
(draft, action) => {
switch (action.type) {
case "toggle":
const todo = draft.find((todo) => todo.id === action.id);
todo.done = !todo.done;
break;
case "add":
draft.push({
id: action.id,
title: "A new todo",
done: false
});
break;
default:
break;
}
},
[ /* initial todos */ ]
);

Conclusion

Immer is a great way to keep the update handlers concise, especially if there’s nesting in your state, and reduce copying repetitive code. It’s an effective solution for those of you looking to mix useState and useReducer with more complex data structures such as deeply nested arrays.

Unlock 10x development with independent components

Building monolithic apps means all your code is internal and is not useful anywhere else. It just serves this one project. And as you scale to more code and people, development becomes slow and painful as everyone works in one codebase and on the same version.

But what if you build independent components first, and then use them to build any number of projects? You could accelerate and scale modern development 10x.

OSS Tools like Bit offer a powerful developer experience for building independent components and composing modular applications. Many teams start by building their Design Systems or Micro Frontends, through independent components. Give it a try →

An independent product component: watch the auto-generated dependency graph

A Better Way to Handle State with Immer was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Bits and Pieces - Medium and was authored by Nivetha Krishnan


Print Share Comment Cite Upload Translate Updates
APA

Nivetha Krishnan | Sciencx (2022-02-14T08:03:25+00:00) A Better Way to Handle State with Immer. Retrieved from https://www.scien.cx/2022/02/14/a-better-way-to-handle-state-with-immer/

MLA
" » A Better Way to Handle State with Immer." Nivetha Krishnan | Sciencx - Monday February 14, 2022, https://www.scien.cx/2022/02/14/a-better-way-to-handle-state-with-immer/
HARVARD
Nivetha Krishnan | Sciencx Monday February 14, 2022 » A Better Way to Handle State with Immer., viewed ,<https://www.scien.cx/2022/02/14/a-better-way-to-handle-state-with-immer/>
VANCOUVER
Nivetha Krishnan | Sciencx - » A Better Way to Handle State with Immer. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2022/02/14/a-better-way-to-handle-state-with-immer/
CHICAGO
" » A Better Way to Handle State with Immer." Nivetha Krishnan | Sciencx - Accessed . https://www.scien.cx/2022/02/14/a-better-way-to-handle-state-with-immer/
IEEE
" » A Better Way to Handle State with Immer." Nivetha Krishnan | Sciencx [Online]. Available: https://www.scien.cx/2022/02/14/a-better-way-to-handle-state-with-immer/. [Accessed: ]
rf:citation
» A Better Way to Handle State with Immer | Nivetha Krishnan | Sciencx | https://www.scien.cx/2022/02/14/a-better-way-to-handle-state-with-immer/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.