Files
2025-11-30 08:25:53 +08:00

120 lines
2.8 KiB
TypeScript

/**
* Persistent Zustand Store (localStorage/sessionStorage)
*
* Use when:
* - State needs to survive page reloads
* - User preferences (theme, language, etc.)
* - Shopping carts, draft forms
*
* Features:
* - Automatic save to localStorage
* - Automatic restore on load
* - Migration support for schema changes
* - Partial persistence (only save some fields)
*
* Learn more: See SKILL.md Issue #1 for Next.js hydration handling
*/
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
interface User {
id: string
name: string
email: string
}
interface PersistedStore {
// State
theme: 'light' | 'dark' | 'system'
language: string
user: User | null
preferences: {
notifications: boolean
emailUpdates: boolean
}
// Actions
setTheme: (theme: PersistedStore['theme']) => void
setLanguage: (language: string) => void
setUser: (user: User | null) => void
updatePreferences: (prefs: Partial<PersistedStore['preferences']>) => void
reset: () => void
}
const initialState = {
theme: 'system' as const,
language: 'en',
user: null,
preferences: {
notifications: true,
emailUpdates: false,
},
}
export const usePersistedStore = create<PersistedStore>()(
persist(
(set) => ({
...initialState,
// Actions
setTheme: (theme) => set({ theme }),
setLanguage: (language) => set({ language }),
setUser: (user) => set({ user }),
updatePreferences: (prefs) =>
set((state) => ({
preferences: { ...state.preferences, ...prefs },
})),
reset: () => set(initialState),
}),
{
name: 'app-storage', // unique name in localStorage
// Optional: use sessionStorage instead
// storage: createJSONStorage(() => sessionStorage),
// Optional: only persist specific fields
// partialize: (state) => ({
// theme: state.theme,
// language: state.language,
// preferences: state.preferences,
// // Don't persist user (privacy)
// }),
// Optional: version and migration for schema changes
version: 1,
migrate: (persistedState: any, version) => {
if (version === 0) {
// Migration from version 0 to 1
// Example: rename field
persistedState.language = persistedState.lang || 'en'
delete persistedState.lang
}
return persistedState
},
},
),
)
/**
* Usage in component:
*
* function ThemeToggle() {
* const theme = usePersistedStore((state) => state.theme)
* const setTheme = usePersistedStore((state) => state.setTheme)
*
* return (
* <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
* Toggle theme
* </button>
* )
* }
*
* NEXT.JS WARNING:
* For Next.js with SSR, see nextjs-store.ts template for hydration handling!
*/