A small, fast and scalable state management solution for React applications using simplified flux principles
Helper functions for shallow comparison, state optimization, and performance improvements in Zustand applications.
Deep equality comparison function for optimizing React re-renders and state subscriptions.
/**
* Performs shallow comparison between two values
* Compares primitive values and top-level properties of objects/arrays
* @param valueA - First value to compare
* @param valueB - Second value to compare
* @returns true if values are shallowly equal, false otherwise
*/
function shallow<T>(valueA: T, valueB: T): boolean;Supported Data Types:
Usage Examples:
import { shallow } from "zustand/shallow";
// Primitive comparisons
shallow(1, 1); // true
shallow("hello", "hello"); // true
shallow(true, false); // false
// Object comparisons (shallow only)
shallow({ a: 1, b: 2 }, { a: 1, b: 2 }); // true
shallow({ a: 1, b: 2 }, { b: 2, a: 1 }); // true (order independent)
shallow({ a: { x: 1 } }, { a: { x: 1 } }); // false (nested objects)
// Array comparisons
shallow([1, 2, 3], [1, 2, 3]); // true
shallow([1, [2, 3]], [1, [2, 3]]); // false (nested arrays)
// Map comparisons
shallow(new Map([["a", 1]]), new Map([["a", 1]])); // true
shallow(new Map([["a", 1], ["b", 2]]), new Map([["b", 2], ["a", 1]])); // true
// Set comparisons
shallow(new Set([1, 2]), new Set([1, 2])); // true
shallow(new Set([1, 2]), new Set([2, 1])); // true (order independent)
// Mixed types
shallow(null, undefined); // false
shallow([], {}); // falseReact hook that provides memoized shallow comparison for optimizing selector functions.
/**
* React hook for memoized shallow comparison of selector results
* Prevents unnecessary re-renders when selector returns shallowly equal values
* @param selector - Function that selects values from state
* @returns Memoized selector function
*/
function useShallow<S, U>(selector: (state: S) => U): (state: S) => U;Usage Examples:
import { create } from "zustand";
import { useShallow } from "zustand/react/shallow";
const useStore = create((set) => ({
user: { name: "John", age: 30 },
posts: [],
preferences: { theme: "light", lang: "en" },
updateUser: (updates) => set((state) => ({
user: { ...state.user, ...updates }
})),
addPost: (post) => set((state) => ({
posts: [...state.posts, post]
})),
}));
// ❌ Bad - creates new object on every render, causes unnecessary re-renders
function UserProfile() {
const { user, preferences } = useStore((state) => ({
user: state.user,
preferences: state.preferences
}));
return <div>{user.name} - {preferences.theme}</div>;
}
// ✅ Good - uses shallow comparison to prevent unnecessary re-renders
function UserProfileOptimized() {
const { user, preferences } = useStore(
useShallow((state) => ({
user: state.user,
preferences: state.preferences
}))
);
return <div>{user.name} - {preferences.theme}</div>;
}
// ✅ Alternative - select individual properties
function UserProfileAlternative() {
const user = useStore((state) => state.user);
const preferences = useStore((state) => state.preferences);
return <div>{user.name} - {preferences.theme}</div>;
}
// Complex selector with derived state
function PostSummary() {
const summary = useStore(
useShallow((state) => ({
totalPosts: state.posts.length,
userPosts: state.posts.filter(p => p.authorId === state.user.id),
recentPosts: state.posts.slice(-5),
}))
);
return (
<div>
<p>Total: {summary.totalPosts}</p>
<p>Yours: {summary.userPosts.length}</p>
<p>Recent: {summary.recentPosts.length}</p>
</div>
);
}Alternative store creation and hook APIs that support custom equality functions.
/**
* Creates store with default equality function for all selectors
* @param initializer - State creator function
* @param defaultEqualityFn - Default equality function
* @returns Store with equality function support
*/
function createWithEqualityFn<T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(
initializer: StateCreator<T, [], Mos>,
defaultEqualityFn?: <U>(a: U, b: U) => boolean
): UseBoundStoreWithEqualityFn<Mutate<StoreApi<T>, Mos>>;
/**
* Hook with custom equality function support
* @param api - Store API to subscribe to
* @returns Complete store state
*/
function useStoreWithEqualityFn<S extends ReadonlyStoreApi<unknown>>(
api: S
): ExtractState<S>;
/**
* Hook with selector and custom equality function
* @param api - Store API to subscribe to
* @param selector - Function to select specific state slice
* @param equalityFn - Custom equality function for comparing values
* @returns Selected state slice
*/
function useStoreWithEqualityFn<S extends ReadonlyStoreApi<unknown>, U>(
api: S,
selector: (state: ExtractState<S>) => U,
equalityFn?: (a: U, b: U) => boolean
): U;Usage Examples:
import { createWithEqualityFn, useStoreWithEqualityFn } from "zustand/traditional";
import { shallow } from "zustand/shallow";
// Store with default shallow equality
const useStore = createWithEqualityFn(
(set) => ({
items: [],
filters: { category: "all", active: true },
addItem: (item) => set((state) => ({
items: [...state.items, item]
})),
updateFilters: (newFilters) => set((state) => ({
filters: { ...state.filters, ...newFilters }
})),
}),
shallow // Default equality function for all selectors
);
// Component using default equality
function ItemList() {
const { items, filters } = useStore(); // Uses shallow comparison
return (
<div>
{items
.filter(item => filters.category === "all" || item.category === filters.category)
.map(item => <div key={item.id}>{item.name}</div>)}
</div>
);
}
// Component with custom equality function
function FilteredItems() {
const filteredItems = useStoreWithEqualityFn(
useStore,
(state) => state.items.filter(item =>
state.filters.category === "all" || item.category === state.filters.category
),
(prevItems, currItems) =>
prevItems.length === currItems.length &&
prevItems.every((item, i) => item.id === currItems[i].id)
);
return (
<div>
{filteredItems.map(item => <div key={item.id}>{item.name}</div>)}
</div>
);
}
// Deep equality for complex objects
function UserSettings() {
const settings = useStoreWithEqualityFn(
useStore,
(state) => state.user.settings,
(prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)
);
return (
<div>
<p>Theme: {settings.theme}</p>
<p>Language: {settings.language}</p>
</div>
);
}Best practices for using utilities to optimize application performance.
import { create } from "zustand";
import { useShallow } from "zustand/react/shallow";
const useStore = create((set) => ({
user: { id: 1, name: "John", email: "john@example.com" },
posts: [],
comments: [],
ui: { loading: false, theme: "light" },
// ... actions
}));
// ❌ Avoid - selecting entire state causes re-renders on any change
function BadComponent() {
const state = useStore();
return <div>{state.user.name}</div>;
}
// ❌ Avoid - new object on every render
function BadSelectorComponent() {
const data = useStore((state) => ({
user: state.user,
postCount: state.posts.length
}));
return <div>{data.user.name} has {data.postCount} posts</div>;
}
// ✅ Good - specific property selection
function GoodComponent() {
const userName = useStore((state) => state.user.name);
return <div>{userName}</div>;
}
// ✅ Good - shallow comparison for multiple properties
function GoodMultiSelectComponent() {
const { user, postCount } = useStore(
useShallow((state) => ({
user: state.user,
postCount: state.posts.length
}))
);
return <div>{user.name} has {postCount} posts</div>;
}// ✅ Good - separate action selectors to prevent re-renders
function OptimizedComponent() {
const userName = useStore((state) => state.user.name);
const updateUser = useStore((state) => state.updateUser);
// Actions don't change, so no re-renders from action updates
return (
<div>
<p>{userName}</p>
<button onClick={() => updateUser({ name: "Jane" })}>
Update Name
</button>
</div>
);
}
// ✅ Alternative - access actions imperatively
function ImperativeActionsComponent() {
const userName = useStore((state) => state.user.name);
const handleUpdate = () => {
useStore.getState().updateUser({ name: "Jane" });
};
return (
<div>
<p>{userName}</p>
<button onClick={handleUpdate}>Update Name</button>
</div>
);
}// Optimized equality functions for different use cases
// Array comparison by length and key properties
const arrayByIds = (prev, curr) =>
prev.length === curr.length &&
prev.every((item, i) => item.id === curr[i].id);
// Object comparison by specific properties
const userByBasicInfo = (prev, curr) =>
prev.id === curr.id &&
prev.name === curr.name &&
prev.email === curr.email;
// Date comparison
const dateEquality = (prev, curr) =>
prev.getTime() === curr.getTime();
// Usage in components
function OptimizedUserList() {
const users = useStoreWithEqualityFn(
useStore,
(state) => state.users,
arrayByIds
);
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}Install with Tessl CLI
npx tessl i tessl/npm-zustand