0
# Utilities
1
2
Helper functions for shallow comparison, state optimization, and performance improvements in Zustand applications.
3
4
## Capabilities
5
6
### Shallow Comparison
7
8
Deep equality comparison function for optimizing React re-renders and state subscriptions.
9
10
```typescript { .api }
11
/**
12
* Performs shallow comparison between two values
13
* Compares primitive values and top-level properties of objects/arrays
14
* @param valueA - First value to compare
15
* @param valueB - Second value to compare
16
* @returns true if values are shallowly equal, false otherwise
17
*/
18
function shallow<T>(valueA: T, valueB: T): boolean;
19
```
20
21
**Supported Data Types:**
22
- Primitives: strings, numbers, booleans, BigInt, symbols
23
- Objects: plain objects (top-level properties only)
24
- Arrays: element-by-element comparison
25
- Maps: key-value pair comparison
26
- Sets: element comparison (order independent)
27
- Iterables: ordered element comparison
28
- Functions: reference equality
29
30
**Usage Examples:**
31
32
```typescript
33
import { shallow } from "zustand/shallow";
34
35
// Primitive comparisons
36
shallow(1, 1); // true
37
shallow("hello", "hello"); // true
38
shallow(true, false); // false
39
40
// Object comparisons (shallow only)
41
shallow({ a: 1, b: 2 }, { a: 1, b: 2 }); // true
42
shallow({ a: 1, b: 2 }, { b: 2, a: 1 }); // true (order independent)
43
shallow({ a: { x: 1 } }, { a: { x: 1 } }); // false (nested objects)
44
45
// Array comparisons
46
shallow([1, 2, 3], [1, 2, 3]); // true
47
shallow([1, [2, 3]], [1, [2, 3]]); // false (nested arrays)
48
49
// Map comparisons
50
shallow(new Map([["a", 1]]), new Map([["a", 1]])); // true
51
shallow(new Map([["a", 1], ["b", 2]]), new Map([["b", 2], ["a", 1]])); // true
52
53
// Set comparisons
54
shallow(new Set([1, 2]), new Set([1, 2])); // true
55
shallow(new Set([1, 2]), new Set([2, 1])); // true (order independent)
56
57
// Mixed types
58
shallow(null, undefined); // false
59
shallow([], {}); // false
60
```
61
62
### React Shallow Hook
63
64
React hook that provides memoized shallow comparison for optimizing selector functions.
65
66
```typescript { .api }
67
/**
68
* React hook for memoized shallow comparison of selector results
69
* Prevents unnecessary re-renders when selector returns shallowly equal values
70
* @param selector - Function that selects values from state
71
* @returns Memoized selector function
72
*/
73
function useShallow<S, U>(selector: (state: S) => U): (state: S) => U;
74
```
75
76
**Usage Examples:**
77
78
```typescript
79
import { create } from "zustand";
80
import { useShallow } from "zustand/react/shallow";
81
82
const useStore = create((set) => ({
83
user: { name: "John", age: 30 },
84
posts: [],
85
preferences: { theme: "light", lang: "en" },
86
updateUser: (updates) => set((state) => ({
87
user: { ...state.user, ...updates }
88
})),
89
addPost: (post) => set((state) => ({
90
posts: [...state.posts, post]
91
})),
92
}));
93
94
// ❌ Bad - creates new object on every render, causes unnecessary re-renders
95
function UserProfile() {
96
const { user, preferences } = useStore((state) => ({
97
user: state.user,
98
preferences: state.preferences
99
}));
100
101
return <div>{user.name} - {preferences.theme}</div>;
102
}
103
104
// ✅ Good - uses shallow comparison to prevent unnecessary re-renders
105
function UserProfileOptimized() {
106
const { user, preferences } = useStore(
107
useShallow((state) => ({
108
user: state.user,
109
preferences: state.preferences
110
}))
111
);
112
113
return <div>{user.name} - {preferences.theme}</div>;
114
}
115
116
// ✅ Alternative - select individual properties
117
function UserProfileAlternative() {
118
const user = useStore((state) => state.user);
119
const preferences = useStore((state) => state.preferences);
120
121
return <div>{user.name} - {preferences.theme}</div>;
122
}
123
124
// Complex selector with derived state
125
function PostSummary() {
126
const summary = useStore(
127
useShallow((state) => ({
128
totalPosts: state.posts.length,
129
userPosts: state.posts.filter(p => p.authorId === state.user.id),
130
recentPosts: state.posts.slice(-5),
131
}))
132
);
133
134
return (
135
<div>
136
<p>Total: {summary.totalPosts}</p>
137
<p>Yours: {summary.userPosts.length}</p>
138
<p>Recent: {summary.recentPosts.length}</p>
139
</div>
140
);
141
}
142
```
143
144
### Traditional API with Custom Equality
145
146
Alternative store creation and hook APIs that support custom equality functions.
147
148
```typescript { .api }
149
/**
150
* Creates store with default equality function for all selectors
151
* @param initializer - State creator function
152
* @param defaultEqualityFn - Default equality function
153
* @returns Store with equality function support
154
*/
155
function createWithEqualityFn<T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(
156
initializer: StateCreator<T, [], Mos>,
157
defaultEqualityFn?: <U>(a: U, b: U) => boolean
158
): UseBoundStoreWithEqualityFn<Mutate<StoreApi<T>, Mos>>;
159
160
/**
161
* Hook with custom equality function support
162
* @param api - Store API to subscribe to
163
* @returns Complete store state
164
*/
165
function useStoreWithEqualityFn<S extends ReadonlyStoreApi<unknown>>(
166
api: S
167
): ExtractState<S>;
168
169
/**
170
* Hook with selector and custom equality function
171
* @param api - Store API to subscribe to
172
* @param selector - Function to select specific state slice
173
* @param equalityFn - Custom equality function for comparing values
174
* @returns Selected state slice
175
*/
176
function useStoreWithEqualityFn<S extends ReadonlyStoreApi<unknown>, U>(
177
api: S,
178
selector: (state: ExtractState<S>) => U,
179
equalityFn?: (a: U, b: U) => boolean
180
): U;
181
```
182
183
**Usage Examples:**
184
185
```typescript
186
import { createWithEqualityFn, useStoreWithEqualityFn } from "zustand/traditional";
187
import { shallow } from "zustand/shallow";
188
189
// Store with default shallow equality
190
const useStore = createWithEqualityFn(
191
(set) => ({
192
items: [],
193
filters: { category: "all", active: true },
194
addItem: (item) => set((state) => ({
195
items: [...state.items, item]
196
})),
197
updateFilters: (newFilters) => set((state) => ({
198
filters: { ...state.filters, ...newFilters }
199
})),
200
}),
201
shallow // Default equality function for all selectors
202
);
203
204
// Component using default equality
205
function ItemList() {
206
const { items, filters } = useStore(); // Uses shallow comparison
207
208
return (
209
<div>
210
{items
211
.filter(item => filters.category === "all" || item.category === filters.category)
212
.map(item => <div key={item.id}>{item.name}</div>)}
213
</div>
214
);
215
}
216
217
// Component with custom equality function
218
function FilteredItems() {
219
const filteredItems = useStoreWithEqualityFn(
220
useStore,
221
(state) => state.items.filter(item =>
222
state.filters.category === "all" || item.category === state.filters.category
223
),
224
(prevItems, currItems) =>
225
prevItems.length === currItems.length &&
226
prevItems.every((item, i) => item.id === currItems[i].id)
227
);
228
229
return (
230
<div>
231
{filteredItems.map(item => <div key={item.id}>{item.name}</div>)}
232
</div>
233
);
234
}
235
236
// Deep equality for complex objects
237
function UserSettings() {
238
const settings = useStoreWithEqualityFn(
239
useStore,
240
(state) => state.user.settings,
241
(prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)
242
);
243
244
return (
245
<div>
246
<p>Theme: {settings.theme}</p>
247
<p>Language: {settings.language}</p>
248
</div>
249
);
250
}
251
```
252
253
### Performance Optimization Patterns
254
255
Best practices for using utilities to optimize application performance.
256
257
#### Selector Optimization
258
259
```typescript
260
import { create } from "zustand";
261
import { useShallow } from "zustand/react/shallow";
262
263
const useStore = create((set) => ({
264
user: { id: 1, name: "John", email: "john@example.com" },
265
posts: [],
266
comments: [],
267
ui: { loading: false, theme: "light" },
268
// ... actions
269
}));
270
271
// ❌ Avoid - selecting entire state causes re-renders on any change
272
function BadComponent() {
273
const state = useStore();
274
return <div>{state.user.name}</div>;
275
}
276
277
// ❌ Avoid - new object on every render
278
function BadSelectorComponent() {
279
const data = useStore((state) => ({
280
user: state.user,
281
postCount: state.posts.length
282
}));
283
return <div>{data.user.name} has {data.postCount} posts</div>;
284
}
285
286
// ✅ Good - specific property selection
287
function GoodComponent() {
288
const userName = useStore((state) => state.user.name);
289
return <div>{userName}</div>;
290
}
291
292
// ✅ Good - shallow comparison for multiple properties
293
function GoodMultiSelectComponent() {
294
const { user, postCount } = useStore(
295
useShallow((state) => ({
296
user: state.user,
297
postCount: state.posts.length
298
}))
299
);
300
return <div>{user.name} has {postCount} posts</div>;
301
}
302
```
303
304
#### Action Optimization
305
306
```typescript
307
// ✅ Good - separate action selectors to prevent re-renders
308
function OptimizedComponent() {
309
const userName = useStore((state) => state.user.name);
310
const updateUser = useStore((state) => state.updateUser);
311
312
// Actions don't change, so no re-renders from action updates
313
return (
314
<div>
315
<p>{userName}</p>
316
<button onClick={() => updateUser({ name: "Jane" })}>
317
Update Name
318
</button>
319
</div>
320
);
321
}
322
323
// ✅ Alternative - access actions imperatively
324
function ImperativeActionsComponent() {
325
const userName = useStore((state) => state.user.name);
326
327
const handleUpdate = () => {
328
useStore.getState().updateUser({ name: "Jane" });
329
};
330
331
return (
332
<div>
333
<p>{userName}</p>
334
<button onClick={handleUpdate}>Update Name</button>
335
</div>
336
);
337
}
338
```
339
340
#### Custom Equality Functions
341
342
```typescript
343
// Optimized equality functions for different use cases
344
345
// Array comparison by length and key properties
346
const arrayByIds = (prev, curr) =>
347
prev.length === curr.length &&
348
prev.every((item, i) => item.id === curr[i].id);
349
350
// Object comparison by specific properties
351
const userByBasicInfo = (prev, curr) =>
352
prev.id === curr.id &&
353
prev.name === curr.name &&
354
prev.email === curr.email;
355
356
// Date comparison
357
const dateEquality = (prev, curr) =>
358
prev.getTime() === curr.getTime();
359
360
// Usage in components
361
function OptimizedUserList() {
362
const users = useStoreWithEqualityFn(
363
useStore,
364
(state) => state.users,
365
arrayByIds
366
);
367
368
return (
369
<ul>
370
{users.map(user => <li key={user.id}>{user.name}</li>)}
371
</ul>
372
);
373
}
374
```