Initial commit
This commit is contained in:
26
references/example-reference.md
Normal file
26
references/example-reference.md
Normal 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.
|
||||
303
references/middleware-guide.md
Normal file
303
references/middleware-guide.md
Normal 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
|
||||
302
references/migration-guide.md
Normal file
302
references/migration-guide.md
Normal 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
|
||||
274
references/nextjs-hydration.md
Normal file
274
references/nextjs-hydration.md
Normal 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
|
||||
281
references/typescript-patterns.md
Normal file
281
references/typescript-patterns.md
Normal 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
|
||||
Reference in New Issue
Block a user