Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:25:53 +08:00
commit 36a2b87167
20 changed files with 3513 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
# [TODO: Reference Document Name]
[TODO: This file contains reference documentation that Claude can load when needed.]
[TODO: Delete this file if you don't have reference documentation to provide.]
## Purpose
[TODO: Explain what information this document contains]
## When Claude Should Use This
[TODO: Describe specific scenarios where Claude should load this reference]
## Content
[TODO: Add your reference content here - schemas, guides, specifications, etc.]
---
**Note**: This file is NOT loaded into context by default. Claude will only load it when:
- It determines the information is needed
- You explicitly ask Claude to reference it
- The SKILL.md instructions direct Claude to read it
Keep this file under 10k words for best performance.

View File

@@ -0,0 +1,303 @@
# Zustand Middleware Complete Guide
Complete reference for all Zustand middleware. Load when user asks about persistence, devtools, immer, or custom middleware.
---
## Persist Middleware
### Basic Usage
```typescript
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
const useStore = create<Store>()(
persist(
(set) => ({ /* store */ }),
{ name: 'storage-name' }
)
)
```
### Storage Options
```typescript
// localStorage (default)
storage: createJSONStorage(() => localStorage)
// sessionStorage
storage: createJSONStorage(() => sessionStorage)
// Custom storage
storage: createJSONStorage(() => customStorage)
```
### Partial Persistence
```typescript
persist(
(set) => ({ /* store */ }),
{
name: 'storage',
partialize: (state) => ({
theme: state.theme,
// Don't persist sensitive data
}),
}
)
```
### Schema Migration
```typescript
persist(
(set) => ({ /* store */ }),
{
name: 'storage',
version: 2,
migrate: (persistedState: any, version) => {
if (version === 0) {
// v0 → v1
persistedState.newField = 'default'
}
if (version === 1) {
// v1 → v2
delete persistedState.oldField
}
return persistedState
},
}
)
```
---
## Devtools Middleware
### Basic Usage
```typescript
import { devtools } from 'zustand/middleware'
const useStore = create<Store>()(
devtools(
(set) => ({ /* store */ }),
{ name: 'StoreName' }
)
)
```
### Named Actions
```typescript
increment: () => set(
(state) => ({ count: state.count + 1 }),
undefined,
'counter/increment' // Shows in DevTools
)
```
### Production Toggle
```typescript
devtools(
(set) => ({ /* store */ }),
{
name: 'Store',
enabled: process.env.NODE_ENV === 'development'
}
)
```
---
## Immer Middleware
Allows mutable state updates (Immer handles immutability):
```typescript
import { immer } from 'zustand/middleware/immer'
const useStore = create<Store>()(
immer((set) => ({
todos: [],
addTodo: (text) => set((state) => {
// Mutate directly!
state.todos.push({ id: Date.now(), text })
}),
}))
)
```
**When to use**: Complex nested state updates
---
## Combining Middlewares
### Order Matters
```typescript
// ✅ CORRECT: devtools wraps persist
const useStore = create<Store>()(
devtools(
persist(
(set) => ({ /* store */ }),
{ name: 'storage' }
),
{ name: 'Store' }
)
)
// Shows persist actions in DevTools
```
### Common Combinations
```typescript
// Persist + Devtools
devtools(persist(...), { name: 'Store' })
// Persist + Immer
persist(immer(...), { name: 'storage' })
// All three
devtools(
persist(
immer(...),
{ name: 'storage' }
),
{ name: 'Store' }
)
```
---
## Custom Middleware
### Logger Example
```typescript
const logger = (config) => (set, get, api) => {
return config(
(...args) => {
console.log('Before:', get())
set(...args)
console.log('After:', get())
},
get,
api
)
}
const useStore = create(logger((set) => ({ /* store */ })))
```
### TypeScript Logger
```typescript
import { StateCreator } from 'zustand'
type Logger = <T>(
f: StateCreator<T, [], []>,
name?: string
) => StateCreator<T, [], []>
const logger: Logger = (f, name) => (set, get, store) => {
const loggedSet: typeof set = (...a) => {
set(...(a as Parameters<typeof set>))
console.log(`[${name}]:`, get())
}
return f(loggedSet, get, store)
}
```
---
## Middleware API Reference
### persist()
```typescript
persist<T>(
stateCreator: StateCreator<T>,
options: {
name: string // Storage key (required)
storage?: PersistStorage<T> // Storage engine
partialize?: (state: T) => Partial<T> // Select what to persist
version?: number // Schema version
migrate?: (state: any, version: number) => T // Migration function
merge?: (persisted: any, current: T) => T // Custom merge
onRehydrateStorage?: (state: T) => void // Hydration callback
}
)
```
### devtools()
```typescript
devtools<T>(
stateCreator: StateCreator<T>,
options?: {
name?: string // Store name in DevTools
enabled?: boolean // Enable/disable
}
)
```
### immer()
```typescript
immer<T>(
stateCreator: StateCreator<T>
)
```
---
## Common Patterns
### Reset Store
```typescript
const initialState = { count: 0 }
const useStore = create<Store>()(
persist(
(set) => ({
...initialState,
reset: () => set(initialState),
}),
{ name: 'storage' }
)
)
```
### Clear Persisted Data
```typescript
// Clear localStorage
localStorage.removeItem('storage-name')
// Or programmatically
const useStore = create<Store>()(
persist(
(set) => ({
clearStorage: () => {
localStorage.removeItem('storage-name')
set(initialState)
},
}),
{ name: 'storage-name' }
)
)
```
---
## Official Documentation
- **Persist**: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md
- **Devtools**: https://github.com/pmndrs/zustand/blob/main/docs/middlewares/devtools.md
- **Immer**: https://github.com/pmndrs/zustand/blob/main/docs/middlewares/immer.md

View File

@@ -0,0 +1,302 @@
# 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
<Provider store={store}>
<App />
</Provider>
// Component
const count = useSelector((state) => state.count)
const dispatch = useDispatch()
<button onClick={() => dispatch(increment())}>Increment</button>
```
### After (Zustand)
```typescript
// Store (all in one!)
import { create } from 'zustand'
interface Store {
count: number
increment: () => void
decrement: () => void
}
const useStore = create<Store>()((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)
<button onClick={increment}>Increment</button>
```
**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 (
<CountContext.Provider value={{ count, increment, decrement }}>
{children}
</CountContext.Provider>
)
}
// Hook
function useCount() {
const context = useContext(CountContext)
if (!context) throw new Error('useCount must be within CountProvider')
return context
}
// App
<CountProvider>
<App />
</CountProvider>
// Component
const { count, increment } = useCount()
```
### After (Zustand)
```typescript
// Store
const useStore = create<Store>()((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<Store>((set) => ({ /* ... */ }))
// ✅ v5
const useStore = create<Store>()((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<Store>()(
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<Store>((set) => ({ /* ... */ }))
// ✅ CORRECT
create<Store>()((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<T>()()`
- [ ] 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

View File

@@ -0,0 +1,274 @@
# Zustand + Next.js Hydration Guide
Complete guide for using Zustand with Next.js SSR. Load for Next.js-specific problems.
---
## The Hydration Problem
**Error**: `"Text content does not match server-rendered HTML"`
**Why it happens**:
- Server renders with default state
- Client loads persisted state from localStorage
- Content doesn't match → hydration error
---
## Solution: Hydration Flag Pattern
### Store Setup
```typescript
'use client'
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface Store {
_hasHydrated: boolean // Track hydration
count: number
setHasHydrated: (hydrated: boolean) => void
increment: () => void
}
export const useStore = create<Store>()(
persist(
(set) => ({
_hasHydrated: false,
count: 0,
setHasHydrated: (hydrated) => set({ _hasHydrated: hydrated }),
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{
name: 'storage',
onRehydrateStorage: () => (state) => {
state?.setHasHydrated(true) // Set true when done
},
},
),
)
```
### Component Usage
```typescript
'use client'
function Component() {
const hasHydrated = useStore((state) => state._hasHydrated)
const count = useStore((state) => state.count)
if (!hasHydrated) {
return <div>Loading...</div>
}
return <div>Count: {count}</div>
}
```
---
## Alternative: Accept Flash
If loading state is unacceptable, accept brief flash:
```typescript
'use client'
function Component() {
const count = useStore((state) => state.count)
// Will show default (0) briefly, then correct value
return <div>Count: {count}</div>
}
```
**Trade-off**: Simpler code, but user sees flash.
---
## App Router vs Pages Router
### App Router (Recommended)
```typescript
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
</html>
)
}
// app/page.tsx
'use client'
import { useStore } from './store'
export default function Page() {
return <Component />
}
```
**Note**: Add `'use client'` to components using Zustand.
### Pages Router
```typescript
// pages/_app.tsx
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />
}
// pages/index.tsx
import { useStore } from '../store'
export default function Page() {
return <Component />
}
```
**Note**: No `'use client'` needed in Pages Router.
---
## Server Components
**NEVER** use Zustand in Server Components:
```typescript
// ❌ WRONG - Server Component
export default async function ServerComponent() {
const count = useStore((state) => state.count) // Error!
return <div>{count}</div>
}
// ✅ CORRECT - Client Component
'use client'
export default function ClientComponent() {
const count = useStore((state) => state.count)
return <div>{count}</div>
}
```
---
## SSR with getServerSideProps
```typescript
// pages/index.tsx
import { GetServerSideProps } from 'next'
export const getServerSideProps: GetServerSideProps = async () => {
// Fetch data on server
const data = await fetchData()
return {
props: { data },
}
}
export default function Page({ data }) {
const setData = useStore((state) => state.setData)
// Sync server data to store
useEffect(() => {
setData(data)
}, [data, setData])
return <Component />
}
```
---
## Common Patterns
### Initialize Store from Props
```typescript
'use client'
function Page({ serverData }) {
const setData = useStore((state) => state.setData)
useEffect(() => {
setData(serverData)
}, [serverData, setData])
return <Component />
}
```
### Conditional Rendering
```typescript
'use client'
function Component() {
const hasHydrated = useStore((state) => state._hasHydrated)
const theme = useStore((state) => state.theme)
return (
<div className={hasHydrated ? theme : 'default'}>
{hasHydrated ? <Content /> : <Skeleton />}
</div>
)
}
```
### Progressive Enhancement
```typescript
'use client'
function Component() {
const hasHydrated = useStore((state) => state._hasHydrated)
const preferences = useStore((state) => state.preferences)
// Always render, but enhance after hydration
return (
<div>
<DefaultUI />
{hasHydrated && <EnhancedUI preferences={preferences} />}
</div>
)
}
```
---
## Debugging Hydration Errors
### Enable Strict Mode
```typescript
// next.config.js
module.exports = {
reactStrictMode: true,
}
```
### Check for Differences
```typescript
useEffect(() => {
console.log('Server state:', defaultState)
console.log('Client state:', persistedState)
}, [])
```
### Use React DevTools
Look for red warnings about hydration mismatches.
---
## Official Resources
- **Next.js Hydration**: https://nextjs.org/docs/messages/react-hydration-error
- **Zustand Persist**: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md
- **DEV.to Guide**: https://dev.to/abdulsamad/how-to-use-zustands-persist-middleware-in-nextjs-4lb5

View File

@@ -0,0 +1,281 @@
# Zustand TypeScript Advanced Patterns
Advanced TypeScript patterns and troubleshooting. Load when encountering complex type inference issues.
---
## Basic TypeScript Setup
### The Golden Rule: Double Parentheses
```typescript
// ✅ CORRECT
const useStore = create<Store>()((set) => ({ /* ... */ }))
// ❌ WRONG
const useStore = create<Store>((set) => ({ /* ... */ }))
```
**Why**: Currying syntax enables middleware type inference.
---
## Store Interface Pattern
```typescript
// Define types
interface BearState {
bears: number
}
interface BearActions {
increase: (by: number) => void
decrease: (by: number) => void
}
// Combine
type BearStore = BearState & BearActions
// Use
const useBearStore = create<BearStore>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
decrease: (by) => set((state) => ({ bears: state.bears - by })),
}))
```
---
## Slices Pattern Types
```typescript
import { StateCreator } from 'zustand'
// Define slice type
interface BearSlice {
bears: number
addBear: () => void
}
// Create slice with proper types
const createBearSlice: StateCreator<
BearSlice & FishSlice, // Combined store type
[], // Middleware mutators
[], // Chained middleware
BearSlice // This slice
> = (set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
})
```
---
## Middleware Types
### With Devtools
```typescript
import { StateCreator } from 'zustand'
import { devtools } from 'zustand/middleware'
interface Store {
count: number
increment: () => void
}
const useStore = create<Store>()(
devtools(
(set) => ({
count: 0,
increment: () =>
set(
(state) => ({ count: state.count + 1 }),
undefined,
'increment'
),
}),
{ name: 'Store' }
)
)
```
### With Persist
```typescript
import { persist } from 'zustand/middleware'
const useStore = create<Store>()(
persist(
(set) => ({ /* ... */ }),
{ name: 'storage' }
)
)
```
### With Multiple Middlewares
```typescript
const useStore = create<Store>()(
devtools(
persist(
(set) => ({ /* ... */ }),
{ name: 'storage' }
),
{ name: 'Store' }
)
)
```
---
## Slices with Middleware
```typescript
import { StateCreator } from 'zustand'
import { devtools } from 'zustand/middleware'
const createBearSlice: StateCreator<
BearSlice & FishSlice,
[['zustand/devtools', never]], // Add devtools mutator
[],
BearSlice
> = (set) => ({
bears: 0,
addBear: () =>
set(
(state) => ({ bears: state.bears + 1 }),
undefined,
'bear/add'
),
})
```
---
## Selector Types
### Basic Selector
```typescript
const bears = useStore((state) => state.bears)
// Type: number
```
### Computed Selector
```typescript
const selectTotal = (state: Store) => state.items.reduce((sum, item) => sum + item.price, 0)
const total = useStore(selectTotal)
// Type: number
```
### Parameterized Selector
```typescript
const selectById = (id: string) => (state: Store) =>
state.items.find((item) => item.id === id)
const item = useStore(selectById('123'))
// Type: Item | undefined
```
---
## Vanilla Store Types
```typescript
import { createStore } from 'zustand/vanilla'
const store = createStore<Store>()((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
// Get state
const state = store.getState()
// Type: Store
// Subscribe
const unsubscribe = store.subscribe((state) => {
// state is typed as Store
})
```
---
## Custom Hook with Types
```typescript
import { useStore } from 'zustand'
const bearStore = createStore<BearStore>()((set) => ({
bears: 0,
increase: () => set((state) => ({ bears: state.bears + 1 })),
}))
// Create custom hook
function useBearStore<T>(selector: (state: BearStore) => T): T {
return useStore(bearStore, selector)
}
```
---
## Common Type Errors
### Error: Type inference breaks
**Problem**: Using single parentheses
```typescript
// ❌ WRONG
const useStore = create<Store>((set) => ({ /* ... */ }))
```
**Solution**: Use double parentheses
```typescript
// ✅ CORRECT
const useStore = create<Store>()((set) => ({ /* ... */ }))
```
### Error: StateCreator types fail
**Problem**: Missing middleware mutators
```typescript
// ❌ WRONG
const createSlice: StateCreator<CombinedStore, [], [], MySlice>
```
**Solution**: Add middleware mutators
```typescript
// ✅ CORRECT
const createSlice: StateCreator<
CombinedStore,
[['zustand/devtools', never]], // Add this
[],
MySlice
>
```
### Error: Circular reference
**Problem**: Slices referencing each other
**Solution**: Define combined type first, then reference in slices
```typescript
type AllSlices = BearSlice & FishSlice & SharedSlice
const createBearSlice: StateCreator<AllSlices, [], [], BearSlice> = ...
```
---
## Official TypeScript Guide
https://zustand.docs.pmnd.rs/guides/typescript