install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/state-zustand-persist" ~/.claude/skills/intense-visions-harness-engineering-state-zustand-persist && rm -rf "$T"
manifest:
agents/skills/claude-code/state-zustand-persist/SKILL.mdsource content
Zustand Persist
Persist Zustand store to localStorage or custom storage with automatic rehydration and migration support
When to Use
- Preserving user preferences, theme settings, or cart contents across page reloads
- Building offline-capable features that need state survival
- Selectively persisting some state fields while keeping others ephemeral
- Migrating persisted state when the store shape changes between versions
Instructions
- Wrap the store creator with the
middleware frompersist
.zustand/middleware - Provide a
— this is the localStorage key. Make it unique per store.name - Use
to persist only specific fields. By default, the entire store is persisted.partialize - Use
to change the storage backend (sessionStorage, IndexedDB, AsyncStorage).storage - Use
andversion
to handle schema changes between app versions.migrate - Handle the rehydration delay — state is synchronously initialized with defaults, then asynchronously rehydrated from storage.
// stores/settings-store.ts import { create } from 'zustand'; import { persist, createJSONStorage } from 'zustand/middleware'; interface SettingsStore { theme: 'light' | 'dark'; locale: string; sidebarOpen: boolean; setTheme: (theme: 'light' | 'dark') => void; setLocale: (locale: string) => void; toggleSidebar: () => void; } export const useSettingsStore = create<SettingsStore>()( persist( (set) => ({ theme: 'light', locale: 'en', sidebarOpen: true, setTheme: (theme) => set({ theme }), setLocale: (locale) => set({ locale }), toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })), }), { name: 'app-settings', // Only persist theme and locale — not sidebarOpen partialize: (state) => ({ theme: state.theme, locale: state.locale, }), } ) );
Details
Rehydration timing: On first render, the store uses the default values defined in
create. The persisted values load asynchronously from storage. Use onRehydrateStorage to detect when rehydration completes:
persist(storeCreator, { name: 'app-settings', onRehydrateStorage: () => { return (state, error) => { if (error) console.error('Rehydration failed', error); else console.log('Rehydrated', state); }; }, });
Waiting for rehydration in components:
// Option 1: useStore.persist.hasHydrated() function App() { const hasHydrated = useSettingsStore.persist.hasHydrated(); if (!hasHydrated) return <Spinner />; return <Main />; } // Option 2: onFinishHydration listener useEffect(() => { const unsub = useSettingsStore.persist.onFinishHydration(() => { setReady(true); }); return unsub; }, []);
Custom storage: For non-localStorage backends:
const indexedDBStorage = createJSONStorage(() => ({ getItem: async (name) => { /* read from IndexedDB */ }, setItem: async (name, value) => { /* write to IndexedDB */ }, removeItem: async (name) => { /* delete from IndexedDB */ }, })); persist(storeCreator, { name: 'key', storage: indexedDBStorage });
Migrations:
persist(storeCreator, { name: 'app-settings', version: 2, migrate: (persistedState, version) => { const state = persistedState as any; if (version < 2) { // v1 had 'darkMode: boolean', v2 uses 'theme: string' state.theme = state.darkMode ? 'dark' : 'light'; delete state.darkMode; } return state; }, });
What NOT to persist: Loading states, error messages, transient UI state, data that should be fetched fresh from the server.
Source
https://zustand.docs.pmnd.rs/middlewares/persist
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.