# Migrating to Zustand
Guide for migrating from Redux, Context API, or Zustand v4. Load when user is migrating state management.
---
## From Redux to Zustand
### Before (Redux)
```typescript
// Action types
const INCREMENT = 'INCREMENT'
const DECREMENT = 'DECREMENT'
// Actions
const increment = () => ({ type: INCREMENT })
const decrement = () => ({ type: DECREMENT })
// Reducer
const reducer = (state = { count: 0 }, action) => {
switch (action.type) {
case INCREMENT:
return { count: state.count + 1 }
case DECREMENT:
return { count: state.count - 1 }
default:
return state
}
}
// Store
const store = createStore(reducer)
// Provider
// Component
const count = useSelector((state) => state.count)
const dispatch = useDispatch()
```
### After (Zustand)
```typescript
// Store (all in one!)
import { create } from 'zustand'
interface Store {
count: number
increment: () => void
decrement: () => void
}
const useStore = create()((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
// No provider needed!
// Component
const count = useStore((state) => state.count)
const increment = useStore((state) => state.increment)
```
**Benefits**:
- ~90% less boilerplate
- No provider wrapper
- No action types/creators
- No reducer switch
- Built-in TypeScript support
---
## From Context API to Zustand
### Before (Context)
```typescript
// Context
const CountContext = createContext(null)
// Provider
function CountProvider({ children }) {
const [count, setCount] = useState(0)
const increment = () => setCount((c) => c + 1)
const decrement = () => setCount((c) => c - 1)
return (
{children}
)
}
// Hook
function useCount() {
const context = useContext(CountContext)
if (!context) throw new Error('useCount must be within CountProvider')
return context
}
// App
// Component
const { count, increment } = useCount()
```
### After (Zustand)
```typescript
// Store
const useStore = create()((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
// Component (no provider needed!)
const count = useStore((state) => state.count)
const increment = useStore((state) => state.increment)
```
**Benefits**:
- No provider needed
- No context null checks
- No wrapper components
- Better performance (no context re-renders)
- Persistent by default (with middleware)
---
## From Zustand v4 to v5
### Breaking Changes
#### 1. TypeScript Syntax
```typescript
// ❌ v4
const useStore = create((set) => ({ /* ... */ }))
// ✅ v5
const useStore = create()((set) => ({ /* ... */ }))
// ^^ Double parentheses required
```
#### 2. Persist Middleware Imports
```typescript
// ❌ v4
import { persist } from 'zustand/middleware'
const useStore = create(
persist((set) => ({ /* ... */ }), { name: 'storage' })
)
// ✅ v5
import { persist, createJSONStorage } from 'zustand/middleware'
const useStore = create()(
persist(
(set) => ({ /* ... */ }),
{
name: 'storage',
storage: createJSONStorage(() => localStorage),
}
)
)
```
#### 3. Shallow Import
```typescript
// ❌ v4
import shallow from 'zustand/shallow'
// ✅ v5
import { shallow } from 'zustand/shallow'
```
---
## Migration Strategies
### Gradual Migration
1. **Install Zustand** alongside existing solution
2. **Migrate one feature** at a time
3. **Test thoroughly** before moving to next
4. **Remove old code** once stable
### Big Bang Migration
1. **Create Zustand stores** for all state
2. **Update all components** at once
3. **Remove old state management** entirely
4. **Test everything**
**Recommendation**: Gradual migration for large apps.
---
## Code Mapping
### Redux → Zustand
| Redux | Zustand |
|-------|---------|
| Actions | Direct functions in store |
| Action types | Not needed |
| Reducers | Inline in `set()` calls |
| `useSelector` | Direct store selectors |
| `useDispatch` | Direct function calls |
| Provider | Not needed |
| Middleware | Built-in (`persist`, `devtools`) |
| DevTools | `devtools` middleware |
### Context → Zustand
| Context | Zustand |
|---------|---------|
| `createContext` | `create()` |
| Provider | Not needed |
| `useContext` | Direct store access |
| State | Store state |
| Updaters | Store actions |
---
## Common Pitfalls
### 1. Forgetting Double Parentheses (v5)
```typescript
// ❌ WRONG
create((set) => ({ /* ... */ }))
// ✅ CORRECT
create()((set) => ({ /* ... */ }))
```
### 2. Creating Objects in Selectors
```typescript
// ❌ WRONG - Causes infinite renders
const { a, b } = useStore((state) => ({ a: state.a, b: state.b }))
// ✅ CORRECT
const a = useStore((state) => state.a)
const b = useStore((state) => state.b)
```
### 3. Not Using Persist Correctly
```typescript
// ❌ WRONG - Missing createJSONStorage
persist((set) => ({ /* ... */ }), { name: 'storage' })
// ✅ CORRECT
persist(
(set) => ({ /* ... */ }),
{
name: 'storage',
storage: createJSONStorage(() => localStorage),
}
)
```
---
## Checklist
- [ ] Installed Zustand v5+
- [ ] Created store with `create()()`
- [ ] Removed Context providers (if migrating from Context)
- [ ] Removed Redux boilerplate (if migrating from Redux)
- [ ] Updated all `useSelector` to Zustand selectors
- [ ] Updated all `useDispatch` to direct function calls
- [ ] Added `persist` if state needs persistence
- [ ] Added `devtools` if using Redux DevTools
- [ ] Tested all components
- [ ] Verified no hydration errors (Next.js)
- [ ] Removed old state management code
---
## Official Resources
- **Zustand Docs**: https://zustand.docs.pmnd.rs/
- **Migration Guide**: https://github.com/pmndrs/zustand/wiki/Migrating-to-v4
- **TypeScript Guide**: https://zustand.docs.pmnd.rs/guides/typescript