zustand
Minimal, unopinionated state management library for React with simple hook-based API, no providers, and minimal boilerplate for global state without Redux complexity.
What this skill does
# Zustand State Management
## Summary
Zustand is a minimal, unopinionated state management library for React. No providers, no boilerplate—just a simple hook-based API that feels natural in React applications.
## When to Use
- React apps needing global state without Redux complexity
- Projects wanting minimal boilerplate and bundle size
- Teams preferring direct state mutations over reducers
- SSR applications (Next.js) requiring flexible state hydration
- Migrating from Redux/Context API to simpler solution
## Quick Start
```bash
npm install zustand
```
```typescript
// stores/useCounterStore.ts
import { create } from 'zustand'
interface CounterState {
count: number
increment: () => void
decrement: () => void
}
export const useCounterStore = create<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
// components/Counter.tsx
import { useCounterStore } from '@/stores/useCounterStore'
export function Counter() {
const { count, increment, decrement } = useCounterStore()
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
)
}
```
---
# Complete Zustand Guide
## Core Concepts
### Store Creation
```typescript
import { create } from 'zustand'
// Basic store
interface BearState {
bears: number
addBear: () => void
}
const useBearStore = create<BearState>((set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
}))
// Store with get access
const useStore = create<State>((set, get) => ({
count: 0,
increment: () => {
const currentCount = get().count
set({ count: currentCount + 1 })
},
}))
```
### State Access Patterns
```typescript
// Select entire store (re-renders on any change)
const state = useStore()
// Select specific fields (re-renders only when these change)
const bears = useStore((state) => state.bears)
const addBear = useStore((state) => state.addBear)
// Destructure with selector
const { bears, addBear } = useStore((state) => ({
bears: state.bears,
addBear: state.addBear,
}))
// Multiple selectors
const bears = useStore((state) => state.bears)
const fish = useStore((state) => state.fish)
```
### Mutations
```typescript
interface TodoState {
todos: Todo[]
addTodo: (text: string) => void
toggleTodo: (id: string) => void
removeTodo: (id: string) => void
}
const useTodoStore = create<TodoState>((set) => ({
todos: [],
// Add item
addTodo: (text) => set((state) => ({
todos: [...state.todos, { id: nanoid(), text, completed: false }]
})),
// Update item
toggleTodo: (id) => set((state) => ({
todos: state.todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
})),
// Remove item
removeTodo: (id) => set((state) => ({
todos: state.todos.filter(todo => todo.id !== id)
})),
}))
```
## React Integration
### useStore Hook
```typescript
function BearCounter() {
// Re-renders when bears changes
const bears = useBearStore((state) => state.bears)
return <h1>{bears} bears around here...</h1>
}
function Controls() {
// Doesn't re-render when bears changes
const addBear = useBearStore((state) => state.addBear)
return <button onClick={addBear}>Add bear</button>
}
```
### Shallow Comparison
```typescript
import { shallow } from 'zustand/shallow'
// Prevent re-renders when object identity changes but values don't
const { nuts, honey } = useBearStore(
(state) => ({ nuts: state.nuts, honey: state.honey }),
shallow
)
// Custom equality function
const treats = useBearStore(
(state) => state.treats,
(prev, next) => prev.length === next.length
)
```
### Outside React Components
```typescript
// Read state
const count = useStore.getState().count
// Subscribe to changes
const unsubscribe = useStore.subscribe(
(state) => console.log('Count changed:', state.count)
)
// Update state
useStore.setState({ count: 42 })
// Update with function
useStore.setState((state) => ({ count: state.count + 1 }))
```
## TypeScript Patterns
### Typed Store Creation
```typescript
interface UserState {
user: User | null
setUser: (user: User) => void
clearUser: () => void
}
const useUserStore = create<UserState>((set) => ({
user: null,
setUser: (user) => set({ user }),
clearUser: () => set({ user: null }),
}))
// Type inference works automatically
const user = useUserStore((state) => state.user) // User | null
```
### Store Type Inference
```typescript
// Extract store type
type UserStoreState = ReturnType<typeof useUserStore.getState>
// Selector type helper
type Selector<T> = (state: UserState) => T
const selectUsername: Selector<string | undefined> = (state) =>
state.user?.name
```
### Combining Multiple Stores
```typescript
// Type-safe store combination
function useHybridStore<T, U>(
selector1: (state: State1) => T,
selector2: (state: State2) => U
): [T, U] {
return [
useStore1(selector1),
useStore2(selector2),
]
}
const [user, theme] = useHybridStore(
(s) => s.user,
(s) => s.theme
)
```
## Slices Pattern
### Creating Slices
```typescript
// authSlice.ts
export interface AuthSlice {
user: User | null
login: (credentials: Credentials) => Promise<void>
logout: () => void
}
export const createAuthSlice: StateCreator<
AuthSlice & TodoSlice,
[],
[],
AuthSlice
> = (set) => ({
user: null,
login: async (credentials) => {
const user = await api.login(credentials)
set({ user })
},
logout: () => set({ user: null }),
})
// todoSlice.ts
export interface TodoSlice {
todos: Todo[]
addTodo: (text: string) => void
}
export const createTodoSlice: StateCreator<
AuthSlice & TodoSlice,
[],
[],
TodoSlice
> = (set) => ({
todos: [],
addTodo: (text) => set((state) => ({
todos: [...state.todos, { id: nanoid(), text, completed: false }]
})),
})
// store.ts
import { create } from 'zustand'
import { createAuthSlice, AuthSlice } from './authSlice'
import { createTodoSlice, TodoSlice } from './todoSlice'
export const useStore = create<AuthSlice & TodoSlice>()((...a) => ({
...createAuthSlice(...a),
...createTodoSlice(...a),
}))
```
### Cross-Slice Communication
```typescript
export const createTodoSlice: StateCreator<
AuthSlice & TodoSlice,
[],
[],
TodoSlice
> = (set, get) => ({
todos: [],
addTodo: (text) => {
// Access other slice's state
const user = get().user
if (!user) throw new Error('Not authenticated')
set((state) => ({
todos: [...state.todos, {
id: nanoid(),
text,
userId: user.id,
completed: false
}]
}))
},
})
```
## Middleware
### Persist Middleware
```typescript
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
interface PreferencesState {
theme: 'light' | 'dark'
language: string
setTheme: (theme: 'light' | 'dark') => void
}
export const usePreferencesStore = create<PreferencesState>()(
persist(
(set) => ({
theme: 'light',
language: 'en',
setTheme: (theme) => set({ theme }),
}),
{
name: 'preferences-storage', // localStorage key
storage: createJSONStorage(() => localStorage),
// Partial persistence
partialize: (state) => ({ theme: state.theme }),
// Migration between versions
version: 1,
migrate: (persistedState: any, version: number) => {
if (version === 0) {
// Migrate from v0 to v1
persistedState.language = 'en'
}
return persistedState as PreferencesState
},
}
)
)
// Custom storage (e.g., AsyncStorage for React Native)
const customStorage = {
getItem: async (name: string) => {
const value = await AsyncStorage.getItem(name)
return value ?? null
},
setItem: async (name: string, value: string) => {
await AsyncStoRelated in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.