React

UseImmer: A Better Alternative to useState

Improving React State Management with UseImmer

Sahil Agarwal
6 min readMay 9, 2023

--

Overview

In React programming, managing state is a fundamental aspect of building dynamic user interfaces. The useState hook is a popular solution for managing state in React components. However, as the complexity of the application grows, managing state using useState can become unwieldy and error-prone. use-immer is a library that provides a better alternative to managing state in React applications. In this article, we will explore how to use UseImmer as an alternative to useState, with code examples explained in detail.

What is use-immer?

use-immer is a library that allows you to manage a state in a more intuitive way, by using a mutable state object, while still preserving the immutability of the original state. The library is built on top of the popular Immer library, which provides a simpler way of working with immutable objects.

Installing use-immer

Before we can start using use-immer, we need to install it as a dependency in our project. We can install it using npm or yarn.

// npm package manager
npm install use-immer

// yarn package manager
yarn add use-immer

Basic Usage

Using UseImmer is similar to using useState. We start by importing the useImmer hook from the use-immer library.

import React, { useState } from “react”; 
import { useImmer } from “use-immer”;

We can then use the useImmer hook to create a state variable, with an initial value.

const [state, setState] = useImmer(initialState);

The state variable is a mutable object, which we can modify directly. We can use the setState function to update the state, just like we would with useState.

setState(draft => {
draft.property = "new value";
});

Here, the draft variable is a mutable copy of the state object, which we can modify directly. Once we are done modifying the draft object, UseImmer creates a new immutable object, which becomes the new value of state.

When to use useImmer over useState

useImmer is a powerful alternative to the useState hook in React, especially when it comes to managing complex states with nested data structures.

One of the main benefits of useImmer is that it simplifies state updates by using a mutable "draft" object that can be modified directly with standard JavaScript syntax, similar to the setState function in class components. This makes it easier to update the state in a more intuitive and concise way, without the need for nested callback functions or spread operators.

For example, consider the following code using useState to manage a list of users with their respective posts:

import React, { useState } from "react";

function UserList() {
const [users, setUsers] = useState([
{
id: 1,
name: "Alice",
posts: [{ id: 1, title: "Post 1" }, { id: 2, title: "Post 2" }],
},
{
id: 2,
name: "Bob",
posts: [{ id: 3, title: "Post 3" }],
},
]);

function addPost(userId, postTitle) {
setUsers(prevUsers => {
return prevUsers.map(user => {
if (user.id === userId) {
return {
...user,
posts: [...user.posts, { id: Date.now(), title: postTitle }],
};
} else {
return user;
}
});
});
}

return (
<div>
<h2>User List</h2>
{users.map(user => (
<div key={user.id}>
<h3>{user.name}</h3>
<ul>
{user.posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
<button onClick={() => addPost(user.id, "New Post")}>
Add Post
</button>
</div>
))}
</div>
);
}

Here, we define a state variable users with an array of user objects, each containing an array of post objects. We define a function addPost that updates the state by creating a new user array with the updated posts array for a given user, using the spread operator to avoid mutating the original state.

This works fine for a small amount of data, but it quickly becomes cumbersome and error-prone when dealing with deeply nested data structures, as it requires a lot of boilerplate code and careful management of immutable updates.

By contrast, useImmer provides a much simpler and more intuitive way to update the state, as demonstrated in the following code:

import React, { useState } from "react";
import { useImmer } from "use-immer";

function UserList() {
const [users, setUsers] = useImmer([
{
id: 1,
name: "Alice",
posts: [{ id: 1, title: "Post 1" }, { id: 2, title: "Post 2" }],
},
{
id: 2,
name: "Bob",
posts: [{ id: 3, title: "Post 3" }],
},
]);

function addPost(userId, postTitle) {
setUsers(draft => {
const user = draft.find(user => user.id === userId);
user.posts.push({ id: Date.now(), title: postTitle });
});
}

return (
<div>
<h2>User List</h2>
{users.map(user => (
<div key={user.id}>
<h3>{user.name}</h3>
<ul>
{user.posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
<button onClick={() => addPost(user.id, "New Post")}>
Add Post
</button>
</div>
))}
</div>

Advantages of useImmer

1. Improved code readability and maintainability: With useImmer, the code for updating the state becomes simpler and more readable, reducing the chances of introducing bugs. This is because the library abstracts the complexity of immutability away, making it easier to reason about the state changes.

2. Performance benefits: useImmer also, provide performance benefits over useState. In useState, React will rerender the entire component whenever the state changes, even if only a small portion of the state has changed. With useImmer, the draft state is only updated when the corresponding state values are changed, reducing the number of unnecessary re-renders and improving performance.

3. Better handling of complex state updates: When dealing with complex state updates, useState can quickly become unwieldy, with nested objects and arrays making it hard to track changes. In contrast, with useImmer, the library handles all the complexity, providing an easy and intuitive way to update the state.

Limitations of useImmer

While UseImmer offers a lot of benefits over the traditional useState hook, it’s important to understand that it also has some limitations. Here are some of the limitations of UseImmer:

  • Nested Updates: UseImmer can be challenging to use with deeply nested data structures. When working with complex data structures, it can be difficult to make updates to individual nested objects or arrays. In such cases, developers might have to resort to writing additional code to work around the limitations of UseImmer.
  • Not Suitable for Simple Applications: UseImmer might not be the best choice for simple applications that don’t require complex state management. Using UseImmer in such cases might actually add unnecessary complexity to the code.
  • Performance Overhead: While UseImmer is generally performant, there can be cases where it adds some performance overhead. This is because UseImmer creates a new copy of the state every time it’s updated, which can be slower than mutating the state directly.
  • Large State Objects: UseImmer might not be the best choice for large state objects. When working with large state objects, UseImmer can consume a lot of memory and slow down the application.

It’s important to understand these limitations and weigh them against the benefits of using UseImmer when deciding whether to use it in a project. In most cases, the benefits of UseImmer outweigh the limitations, but developers should always be mindful of the limitations when using UseImmer.

Conclusion

In conclusion, UseImmer is a superior alternative to useState, simplifying state management in React, and making code more concise and readable. Although it has some limitations such as incompatibility with class components and performance issues with large amounts of data, UseImmer remains a powerful tool for developers seeking to write more efficient and maintainable code.

References

React Official Documentation

Thank you for taking the time to read this article.

If you found it helpful, please consider following me on Medium for more articles on cloud computing, DevOps, and software development.

You can also connect with me on LinkedIn to stay updated on my latest projects and professional endeavors.

As always, feel free to leave any comments or feedback, and I’ll be sure to get back to you!

--

--

Sahil Agarwal

SWE @cimpress | AWS Certified x2 | EY GDS Hackpions x2 | I am an improving Software Engineer who likes exploring new tech. Expertise in MERN stack and AWS.