A small, fast and scalable state management solution for React applications using simplified flux principles
React hooks and components for consuming Zustand stores with automatic re-rendering and selector support.
Hook for consuming store state in React components with automatic re-rendering when selected state changes.
/**
* Subscribe to entire store state
* @param api - Store API to subscribe to
* @returns Complete store state
*/
function useStore<S extends ReadonlyStoreApi<unknown>>(api: S): ExtractState<S>;
/**
* Subscribe to selected portion of store state
* @param api - Store API to subscribe to
* @param selector - Function to select specific state slice
* @returns Selected state slice
*/
function useStore<S extends ReadonlyStoreApi<unknown>, U>(
api: S,
selector: (state: ExtractState<S>) => U
): U;Usage Examples:
import { create } from "zustand";
const useStore = create((set) => ({
count: 0,
name: "John",
increment: () => set((state) => ({ count: state.count + 1 })),
}));
// Subscribe to entire state
function FullStateComponent() {
const state = useStore();
return <div>Count: {state.count}, Name: {state.name}</div>;
}
// Subscribe to specific state slice
function CountComponent() {
const count = useStore((state) => state.count);
return <div>Count: {count}</div>;
}
// Complex selector
function DerivedStateComponent() {
const isEven = useStore((state) => state.count % 2 === 0);
return <div>Count is {isEven ? 'even' : 'odd'}</div>;
}Combined hook and store API interface returned by the create function.
/**
* Hook interface with store API methods
* Can be called as a hook with optional selector
*/
type UseBoundStore<S extends ReadonlyStoreApi<unknown>> = {
/** Subscribe to entire store state */
(): ExtractState<S>;
/** Subscribe to selected state slice */
<U>(selector: (state: ExtractState<S>) => U): U;
} & S;Usage Examples:
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
// Use as hook - entire state
function Component1() {
const { count, increment } = useCounterStore();
return <button onClick={increment}>Count: {count}</button>;
}
// Use as hook - with selector
function Component2() {
const count = useCounterStore((state) => state.count);
return <div>Count: {count}</div>;
}
// Access store API methods directly
function Component3() {
const handleClick = () => {
useCounterStore.getState().increment();
};
return <button onClick={handleClick}>Increment</button>;
}
// Subscribe imperatively
useEffect(() => {
const unsubscribe = useCounterStore.subscribe((state, prevState) => {
console.log('Count changed:', prevState.count, '->', state.count);
});
return unsubscribe;
}, []);Alternative API that provides custom equality function support for preventing unnecessary re-renders.
/**
* 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;
/**
* Create store with default equality function
* @param initializer - Function that creates the initial state and actions
* @param defaultEqualityFn - Default equality function for all selectors
* @returns UseBoundStoreWithEqualityFn instance
*/
function createWithEqualityFn<T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(
initializer: StateCreator<T, [], Mos>,
defaultEqualityFn?: <U>(a: U, b: U) => boolean
): UseBoundStoreWithEqualityFn<Mutate<StoreApi<T>, Mos>>;Usage Examples:
import { createWithEqualityFn, useStoreWithEqualityFn } from "zustand/traditional";
// Store with default equality function
const useStore = createWithEqualityFn(
(set) => ({
users: [],
addUser: (user) => set((state) => ({ users: [...state.users, user] })),
}),
(a, b) => JSON.stringify(a) === JSON.stringify(b) // deep equality
);
// Use with custom equality function
function UserList() {
const users = useStoreWithEqualityFn(
useStore,
(state) => state.users,
(a, b) => a.length === b.length && a.every((user, i) => user.id === b[i].id)
);
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}Type for store APIs that only expose read operations, used for type safety in hooks.
/**
* Read-only interface for store API
* Excludes setState to prevent mutations from components
*/
type ReadonlyStoreApi<T> = Pick<StoreApi<T>, 'getState' | 'getInitialState' | 'subscribe'>;Best practices for optimizing React component re-renders with Zustand.
Selector Optimization:
// ❌ Bad - creates new object every render, causes unnecessary re-renders
const { user, posts } = useStore((state) => ({
user: state.user,
posts: state.posts
}));
// ✅ Good - select minimal state
const user = useStore((state) => state.user);
const posts = useStore((state) => state.posts);
// ✅ Good - use shallow comparison for objects
import { useShallow } from "zustand/react/shallow";
const { user, posts } = useStore(useShallow((state) => ({
user: state.user,
posts: state.posts
})));Action Separation:
// ✅ Good - separate actions to prevent re-renders when actions change
const count = useStore((state) => state.count);
const increment = useStore((state) => state.increment);
// Or access actions directly from store
const handleIncrement = () => useStore.getState().increment();Install with Tessl CLI
npx tessl i tessl/npm-zustand