immer
The immer middleware allows you to use Immer to update state with mutable syntax while maintaining immutability. Since the immer middleware doesn't require special configuration for multiple instances, zustand-context fully supports the original immer middleware from Zustand.
note
When using middleware with zustand-context, you must use the double function call pattern: create()()
. This applies to both TypeScript and JavaScript and ensures proper type inference and middleware compatibility.
Installation
To use the immer middleware, you need to install both the zustand
and immer
packages:
- npm
- yarn
- pnpm
npm install zustand immer
yarn add zustand immer
pnpm add zustand immer
Basic Usage
- TypeScript
- JavaScript
import { create } from '@mag1yar/zustand-context';
import { immer } from 'zustand/middleware/immer';
interface TodoState {
todos: Array<{ id: string; text: string; completed: boolean }>;
addTodo: (text: string) => void;
toggleTodo: (id: string) => void;
removeTodo: (id: string) => void;
}
const useTodoStore = create<TodoState>()(
immer((set) => ({
todos: [],
addTodo: (text) =>
set((state) => {
state.todos.push({
id: crypto.randomUUID(),
text,
completed: false,
});
}),
toggleTodo: (id) =>
set((state) => {
const todo = state.todos.find((t) => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
}),
removeTodo: (id) =>
set((state) => {
const index = state.todos.findIndex((t) => t.id === id);
if (index !== -1) {
state.todos.splice(index, 1);
}
}),
})),
{
name: 'TodoContext',
},
);
import { create } from '@mag1yar/zustand-context';
import { immer } from 'zustand/middleware/immer';
const useTodoStore = create()(
immer((set) => ({
todos: [],
addTodo: (text) =>
set((state) => {
state.todos.push({
id: crypto.randomUUID(),
text,
completed: false,
});
}),
toggleTodo: (id) =>
set((state) => {
const todo = state.todos.find((t) => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
}),
removeTodo: (id) =>
set((state) => {
const index = state.todos.findIndex((t) => t.id === id);
if (index !== -1) {
state.todos.splice(index, 1);
}
}),
})),
{
name: 'TodoContext',
},
);
Working with Nested State
Immer excels at handling deeply nested state updates:
- TypeScript
- JavaScript
import { create } from '@mag1yar/zustand-context';
import { immer } from 'zustand/middleware/immer';
interface UserState {
users: Record<
string,
{
profile: {
name: string;
email: string;
settings: {
theme: 'light' | 'dark';
notifications: boolean;
};
};
posts: Array<{ id: string; content: string }>;
}
>;
updateUserTheme: (userId: string, theme: 'light' | 'dark') => void;
addPost: (userId: string, content: string) => void;
}
const useUserStore = create<UserState>()(
immer((set) => ({
users: {},
updateUserTheme: (userId, theme) =>
set((state) => {
if (state.users[userId]) {
state.users[userId].profile.settings.theme = theme;
}
}),
addPost: (userId, content) =>
set((state) => {
if (state.users[userId]) {
state.users[userId].posts.push({
id: crypto.randomUUID(),
content,
});
}
}),
})),
{
name: 'UserContext',
},
);
import { create } from '@mag1yar/zustand-context';
import { immer } from 'zustand/middleware/immer';
const useUserStore = create()(
immer((set) => ({
users: {},
updateUserTheme: (userId, theme) =>
set((state) => {
if (state.users[userId]) {
state.users[userId].profile.settings.theme = theme;
}
}),
addPost: (userId, content) =>
set((state) => {
if (state.users[userId]) {
state.users[userId].posts.push({
id: crypto.randomUUID(),
content,
});
}
}),
})),
{
name: 'UserContext',
},
);