My Blogs

About me

Simplifying React State Updates with use-immer

LAST UPDATED AT: Aug 13, 2025

Immer is a library to 'ease' updating state of immutable data in javascript (like objects, arrays). The benefits become even more apparent with deeply nested state structures.

Consider, the state which is a product list:

{ name: "Product 1", SKU: "SKU-001", availability: 30, stock: [ { id: 1, store: "Store 1", quantity: 10, }, { id: 2, store: "Store 2", quantity: 20, }, ], }

Suppose we are giving the user a button which will update the stock's quantity by 1. The code to make it work will look like this:

const updateItemPrice = (index: number) => { const updatedProducts = { ...products, stock: [...products.stock].map((s, i) => { if (index === i) { return { ...s, quantity: s.quantity + 1 }; } else { return s; } }), }; setProducts(updatedProducts); };

The reason behind the verbose part of updating the stock by index is because to trigger a state update of object, React will have to get a fresh copy of object.

If we do products[0].stock.quantity++,

then it is only mutating the object but the object as a whole stays the same

But what if there is a way to update it in a good straightforward manner ?

Immer is a library to get immutable state by mutating the current one. For the purpose of react hook state, immer provides a separate package named use-immer.

Lets update our code of updating index using this library.

First, instead of useState we have to use the hook named useImmer from use-immer.

The updating function is now :

const updateItemPrice = (index: number) => { setProducts((draft) => { draft.stock[index].quantity = draft.stock[index].quantity + 1; }); };

Understanding the Draft Function

The function passed to setProducts is called a Draft Function ↗️. The draft parameter is not the actual state object, but a special proxy object created by Immer that:

  • Allows you to write mutations directly (like draft.stock[index].quantity++)
  • Records all changes you make
  • Automatically creates a new immutable state based on your mutations
  • Ensures React detects the state change and triggers re-renders

Think of draft as a "workspace" where you can safely modify the state as if it were mutable, while Immer handles the immutability behind the scenes.

Conclusion

Use-immer and Immer transforms complex state updates from verbose, error-prone spread operations into simple, readable mutations.

It maintains React's immutability requirements while providing the intuitive experience of direct object modification. This makes your code more maintainable, less buggy, and significantly easier to reason about—especially when dealing with deeply nested state structures.

Feel free to check out the code for this in this Codesandbox ↗️ and try it out on your own project!

PS:

use-immer also exports a useImmerReducer which is a immer equivalent to useReducer

chao 👋