useImmer
Immutable state with drafts. Write "mutating" code that produces immutable updates, inspired by Immer.
Live Demo
A todo list demonstrating how useImmer simplifies complex nested state updates.
✅ Todo List with Immer
Learn useImmer
#learning
Build something cool
#project
📝 Event Log
No events yet
💡 How It Works:
- Write "mutating" code that produces immutable updates
- No need for spread operators or complex cloning
- Simpler, more readable state updates
- Works with nested objects and arrays
- Inspired by Immer library
Code Examples
The Problem: Complex Immutable Updates
// ❌ Without Immer - verbose and error-prone
const [state, setState] = useState({ todos: [] });
const toggleTodo = (id) => {
setState(prev => ({
...prev,
todos: prev.todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
}));
};
const addTag = (todoId, tag) => {
setState(prev => ({
...prev,
todos: prev.todos.map(todo =>
todo.id === todoId
? { ...todo, tags: [...todo.tags, tag] }
: todo
)
}));
};The Solution: useImmer
import { useImmer } from "@uiblock/hooks";
// ✅ With Immer - simple and intuitive
const [state, updateState] = useImmer({ todos: [] });
const toggleTodo = (id) => {
updateState(draft => {
const todo = draft.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
});
};
const addTag = (todoId, tag) => {
updateState(draft => {
const todo = draft.todos.find(t => t.id === todoId);
if (todo) {
todo.tags.push(tag);
}
});
};Nested Object Updates
import { useImmer } from "@uiblock/hooks";
function UserProfile() {
const [user, updateUser] = useImmer({
name: 'John',
address: {
street: '123 Main St',
city: 'New York',
country: 'USA'
},
preferences: {
theme: 'dark',
notifications: true
}
});
const updateCity = (newCity) => {
updateUser(draft => {
draft.address.city = newCity;
});
};
const toggleNotifications = () => {
updateUser(draft => {
draft.preferences.notifications = !draft.preferences.notifications;
});
};
return <div>{/* UI */}</div>;
}Array Operations
import { useImmer } from "@uiblock/hooks";
function ShoppingCart() {
const [cart, updateCart] = useImmer({ items: [] });
const addItem = (product) => {
updateCart(draft => {
draft.items.push({
id: product.id,
name: product.name,
quantity: 1
});
});
};
const removeItem = (id) => {
updateCart(draft => {
const index = draft.items.findIndex(item => item.id === id);
if (index !== -1) {
draft.items.splice(index, 1);
}
});
};
const updateQuantity = (id, quantity) => {
updateCart(draft => {
const item = draft.items.find(item => item.id === id);
if (item) {
item.quantity = quantity;
}
});
};
return <div>{/* UI */}</div>;
}useImmerReducer
import { useImmerReducer } from "@uiblock/hooks";
function reducer(draft, action) {
switch (action.type) {
case 'ADD_TODO':
draft.todos.push({
id: Date.now(),
text: action.payload,
completed: false
});
break;
case 'TOGGLE_TODO':
const todo = draft.todos.find(t => t.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
break;
case 'DELETE_TODO':
const index = draft.todos.findIndex(t => t.id === action.payload);
if (index !== -1) {
draft.todos.splice(index, 1);
}
break;
}
}
function TodoApp() {
const [state, dispatch] = useImmerReducer(reducer, { todos: [] });
return (
<div>
<button onClick={() => dispatch({ type: 'ADD_TODO', payload: 'New task' })}>
Add Todo
</button>
{/* ... */}
</div>
);
}API Reference
useImmer(initialState)
initialState: S | (() => S)Initial state or lazy initializer
Returns: [state, updateState] - Current state and updater function
useImmerReducer(reducer, initialState)
reducer: (draft: S, action: A) => void | SReducer function that mutates draft
initialState: SInitial state
Returns: [state, dispatch] - Current state and dispatch function
💡 Use Cases
- Complex nested state updates
- Todo lists and task managers
- Form state management
- Shopping carts
- Tree structures and hierarchical data
- Any state with deep nesting