Use immer with React hooks
use-immer is a React hooks library that bridges Immer's immutable state management with React's hooks API. It provides useImmer and useImmerReducer as direct replacements for useState and useReducer, allowing developers to write straightforward mutative code that Immer automatically converts to immutable updates under the hood.
The package exists to solve a persistent pain point in React development: managing deeply nested state updates. Without use-immer, updating a property several levels deep requires verbose spread syntax at each nesting level, leading to error-prone and hard-to-read code. By leveraging Immer's draft mechanism, developers can write natural JavaScript mutations while maintaining React's immutability requirements.
With over 515,000 weekly downloads, use-immer is widely adopted in React applications that deal with complex state structures like forms, nested configuration objects, or normalized data stores. It's particularly popular in enterprise applications where state trees grow complex and maintainability becomes critical. The library is maintained as part of the Immer ecosystem and follows React's hooks conventions, making it immediately familiar to React developers.
import React from 'react';
import { useImmer } from 'use-immer';
function UserProfileEditor() {
const [profile, updateProfile] = useImmer({
name: 'Alice Johnson',
contact: {
email: 'alice@example.com',
phone: '555-0123',
address: {
street: '123 Main St',
city: 'Boston',
state: 'MA',
zip: '02101'
}
},
preferences: {
notifications: true,
theme: 'light'
}
});
const updateCity = (newCity) => {
updateProfile(draft => {
draft.contact.address.city = newCity;
});
};
const toggleNotifications = () => {
updateProfile(draft => {
draft.preferences.notifications = !draft.preferences.notifications;
});
};
const updateEmail = (e) => {
updateProfile(draft => {
draft.contact.email = e.target.value;
});
};
return (
<div>
<h2>{profile.name}</h2>
<input
type="email"
value={profile.contact.email}
onChange={updateEmail}
/>
<p>City: {profile.contact.address.city}</p>
<button onClick={() => updateCity('Cambridge')}>
Move to Cambridge
</button>
<label>
<input
type="checkbox"
checked={profile.preferences.notifications}
onChange={toggleNotifications}
/>
Enable notifications
</label>
</div>
);
}
export default UserProfileEditor;Complex Form Management: Managing multi-step forms with nested address fields, contact information, and validation state becomes straightforward. Instead of spreading through multiple object levels, you directly mutate draft.personalInfo.address.street and Immer handles the immutable update.
Normalized State Updates: Applications using normalized data structures (entities stored by ID with relationships) benefit significantly. Updating a specific user's nested preferences or settings in a users-by-id map requires just draft.users[userId].settings.theme = 'dark' instead of complex spread operations.
Canvas or Drawing Applications: State for drawing tools, layers, and shape properties often involves deeply nested structures. use-immer lets you write intuitive code like draft.layers[activeLayer].shapes[shapeId].position.x += deltaX without manual immutability gymnastics.
Redux-Style Reducers in Components: Component-level reducers that handle complex action types (like a local data table with filtering, sorting, and pagination) become more readable. Each case statement can directly modify the draft state rather than returning carefully constructed new objects.
Real-Time Collaborative Editing: Applications that apply incremental updates from WebSocket events can use use-immer to cleanly apply each change to local state. The structural sharing Immer provides also helps with change detection for optimistic updates and conflict resolution.
Create your next immutable state by mutating the current one
👻 Primitive and flexible state management for React
🧙 Valtio makes proxy-state simple for React and Vanilla
🐻 Bear necessities for state management in React
npm install use-immerpnpm add use-immerbun add use-immer