Agent Capability Analysis
The zustand-store skill by Intellifill is an open-source community AI agent skill for Claude Code and other IDE workflows, helping agents execute tasks with better context, repeatability, and domain-specific guidance.
Ideal Agent Persona
Perfect for Frontend Agents needing advanced state management capabilities with Zustand stores
Core Value
Empowers agents to create scalable and maintainable state management systems using Zustand's middleware stack, selectors, and hooks, while also enabling persistence and testing of stores
↓ Capabilities Granted for zustand-store
! Prerequisites & Limits
- Requires IntelliFill frontend setup
- Zustand library dependency
- JavaScript expertise needed
Browser Sandbox Environment
⚡️ Ready to unleash?
Experience this Agent in a zero-setup browser environment powered by WebContainers. No installation required.
zustand-store
Install zustand-store, an AI agent skill for AI agent workflows and automation. Works with Claude Code, Cursor, and Windsurf with one-command setup.
Zustand Store Development Skill
This skill provides comprehensive guidance for creating Zustand stores in the IntelliFill frontend (quikadmin-web/).
Table of Contents
- Store Architecture
- Basic Store Pattern
- Middleware Stack
- Selectors and Hooks
- Async Actions
- Persistence
- Testing Stores
- Best Practices
Store Architecture
IntelliFill organizes Zustand stores by domain:
quikadmin-web/src/stores/
├── authStore.ts # Authentication state
├── documentStore.ts # Document management
├── templateStore.ts # Template management
├── knowledgeStore.ts # Knowledge base
├── uiStore.ts # UI state (modals, sidebar, etc.)
└── __tests__/ # Store tests
├── documentStore.test.ts
└── ...
Basic Store Pattern
IntelliFill uses a consistent pattern for all stores.
Simple Store Template
typescript1// quikadmin-web/src/stores/[domain]Store.ts 2import { create } from 'zustand'; 3import { immer } from 'zustand/middleware/immer'; 4import { devtools } from 'zustand/middleware'; 5 6// Types 7interface Item { 8 id: string; 9 name: string; 10 createdAt: string; 11} 12 13interface DomainState { 14 // Data 15 items: Item[]; 16 selectedItem: Item | null; 17 18 // UI State 19 loading: boolean; 20 error: string | null; 21 22 // Actions 23 fetchItems: () => Promise<void>; 24 getItem: (id: string) => Promise<void>; 25 createItem: (data: Partial<Item>) => Promise<void>; 26 updateItem: (id: string, data: Partial<Item>) => Promise<void>; 27 deleteItem: (id: string) => Promise<void>; 28 setSelectedItem: (item: Item | null) => void; 29 clearError: () => void; 30} 31 32// Store 33export const useDomainStore = create<DomainState>()( 34 devtools( 35 immer((set, get) => ({ 36 // Initial state 37 items: [], 38 selectedItem: null, 39 loading: false, 40 error: null, 41 42 // Actions 43 fetchItems: async () => { 44 set({ loading: true, error: null }); 45 try { 46 const response = await api.get('/domain'); 47 set({ items: response.data, loading: false }); 48 } catch (error) { 49 set({ 50 error: 'Failed to fetch items', 51 loading: false, 52 }); 53 } 54 }, 55 56 getItem: async (id: string) => { 57 set({ loading: true, error: null }); 58 try { 59 const response = await api.get(`/domain/${id}`); 60 set({ selectedItem: response.data, loading: false }); 61 } catch (error) { 62 set({ 63 error: 'Failed to fetch item', 64 loading: false, 65 }); 66 } 67 }, 68 69 createItem: async (data: Partial<Item>) => { 70 set({ loading: true, error: null }); 71 try { 72 const response = await api.post('/domain', data); 73 set((state) => { 74 state.items.push(response.data); 75 state.loading = false; 76 }); 77 } catch (error) { 78 set({ 79 error: 'Failed to create item', 80 loading: false, 81 }); 82 throw error; 83 } 84 }, 85 86 updateItem: async (id: string, data: Partial<Item>) => { 87 set({ loading: true, error: null }); 88 try { 89 const response = await api.patch(`/domain/${id}`, data); 90 set((state) => { 91 const index = state.items.findIndex((item) => item.id === id); 92 if (index !== -1) { 93 state.items[index] = response.data; 94 } 95 if (state.selectedItem?.id === id) { 96 state.selectedItem = response.data; 97 } 98 state.loading = false; 99 }); 100 } catch (error) { 101 set({ 102 error: 'Failed to update item', 103 loading: false, 104 }); 105 throw error; 106 } 107 }, 108 109 deleteItem: async (id: string) => { 110 set({ loading: true, error: null }); 111 try { 112 await api.delete(`/domain/${id}`); 113 set((state) => { 114 state.items = state.items.filter((item) => item.id !== id); 115 if (state.selectedItem?.id === id) { 116 state.selectedItem = null; 117 } 118 state.loading = false; 119 }); 120 } catch (error) { 121 set({ 122 error: 'Failed to delete item', 123 loading: false, 124 }); 125 throw error; 126 } 127 }, 128 129 setSelectedItem: (item: Item | null) => { 130 set({ selectedItem: item }); 131 }, 132 133 clearError: () => { 134 set({ error: null }); 135 }, 136 })), 137 { name: 'DomainStore' } 138 ) 139);
Middleware Stack
IntelliFill uses three middleware layers: immer, persist, and devtools.
Middleware Order
typescript1import { create } from 'zustand'; 2import { immer } from 'zustand/middleware/immer'; 3import { persist } from 'zustand/middleware'; 4import { devtools } from 'zustand/middleware'; 5 6// Correct order: devtools(persist(immer(...))) 7export const useStore = create<State>()( 8 devtools( 9 persist( 10 immer((set, get) => ({ 11 // Store implementation 12 })), 13 { 14 name: 'store-name', 15 } 16 ), 17 { name: 'StoreName' } 18 ) 19);
Immer Middleware
Immer allows direct state mutation (safely):
typescript1import { immer } from 'zustand/middleware/immer'; 2 3export const useStore = create<State>()( 4 immer((set, get) => ({ 5 items: [], 6 7 // WITHOUT immer (immutable updates) 8 addItemOld: (item) => { 9 set((state) => ({ 10 items: [...state.items, item], 11 })); 12 }, 13 14 // WITH immer (mutable style) 15 addItem: (item) => { 16 set((state) => { 17 state.items.push(item); // Direct mutation! 18 }); 19 }, 20 21 // Nested updates are easier 22 updateNested: (id, value) => { 23 set((state) => { 24 const item = state.items.find((i) => i.id === id); 25 if (item) { 26 item.nested.deeply.value = value; // Easy! 27 } 28 }); 29 }, 30 })) 31);
Persist Middleware
Persist store state to localStorage:
typescript1import { persist } from 'zustand/middleware'; 2 3export const useStore = create<State>()( 4 persist( 5 immer((set, get) => ({ 6 items: [], 7 preferences: {}, 8 })), 9 { 10 name: 'domain-storage', // localStorage key 11 12 // Partial persistence (only save specific fields) 13 partialPersist: { 14 preferences: true, // Save preferences 15 items: false, // Don't save items 16 }, 17 18 // Version for migrations 19 version: 1, 20 21 // Migration function 22 migrate: (persistedState: any, version: number) => { 23 if (version === 0) { 24 // Migrate from v0 to v1 25 persistedState.newField = 'default'; 26 } 27 return persistedState; 28 }, 29 } 30 ) 31);
Devtools Middleware
Enable Redux DevTools integration:
typescript1import { devtools } from 'zustand/middleware'; 2 3export const useStore = create<State>()( 4 devtools( 5 immer((set, get) => ({ 6 count: 0, 7 8 increment: () => { 9 set((state) => { 10 state.count++; 11 }, false, 'increment'); // Action name in devtools 12 }, 13 14 decrement: () => { 15 set((state) => { 16 state.count--; 17 }, false, { type: 'decrement', payload: -1 }); // Detailed action 18 }, 19 })), 20 { 21 name: 'CounterStore', // Store name in devtools 22 enabled: process.env.NODE_ENV === 'development', // Only in dev 23 } 24 ) 25);
Selectors and Hooks
Optimize re-renders with selectors.
Basic Selectors
typescript1// Component re-renders only when items change 2function ItemList() { 3 const items = useDomainStore((state) => state.items); 4 5 return ( 6 <ul> 7 {items.map((item) => ( 8 <li key={item.id}>{item.name}</li> 9 ))} 10 </ul> 11 ); 12} 13 14// Component re-renders only when loading changes 15function LoadingIndicator() { 16 const loading = useDomainStore((state) => state.loading); 17 18 if (!loading) return null; 19 return <Spinner />; 20}
useShallow for Multiple Values
typescript1import { useShallow } from 'zustand/react/shallow'; 2 3// BAD: Re-renders when ANY store property changes 4function Component() { 5 const { items, loading, error } = useDomainStore(); 6 // ... 7} 8 9// GOOD: Re-renders only when items, loading, or error change 10function Component() { 11 const { items, loading, error } = useDomainStore( 12 useShallow((state) => ({ 13 items: state.items, 14 loading: state.loading, 15 error: state.error, 16 })) 17 ); 18 // ... 19}
Custom Selector Hooks
typescript1// quikadmin-web/src/stores/domainStore.ts 2 3// Export selector hooks for common patterns 4export const useDomainItems = () => 5 useDomainStore((state) => state.items); 6 7export const useDomainLoading = () => 8 useDomainStore((state) => state.loading); 9 10export const useDomainError = () => 11 useDomainStore((state) => state.error); 12 13export const useDomainActions = () => 14 useDomainStore( 15 useShallow((state) => ({ 16 fetchItems: state.fetchItems, 17 createItem: state.createItem, 18 updateItem: state.updateItem, 19 deleteItem: state.deleteItem, 20 })) 21 ); 22 23// Usage in components 24function ItemList() { 25 const items = useDomainItems(); 26 const { createItem, deleteItem } = useDomainActions(); 27 28 // Component only re-renders when items or actions change 29}
Derived State
typescript1export const useDomainStore = create<DomainState>()( 2 immer((set, get) => ({ 3 items: [], 4 filter: '', 5 6 // Computed/derived state 7 get filteredItems() { 8 const { items, filter } = get(); 9 if (!filter) return items; 10 return items.filter((item) => 11 item.name.toLowerCase().includes(filter.toLowerCase()) 12 ); 13 }, 14 15 setFilter: (filter: string) => { 16 set({ filter }); 17 }, 18 })) 19); 20 21// Usage 22function FilteredList() { 23 const filteredItems = useDomainStore((state) => state.filteredItems); 24 return <ul>{filteredItems.map(...)}</ul>; 25}
Async Actions
Handle async operations with proper loading and error states.
Async Action Pattern
typescript1export const useDomainStore = create<DomainState>()( 2 immer((set, get) => ({ 3 items: [], 4 loading: false, 5 error: null, 6 7 fetchItems: async () => { 8 // Set loading state 9 set({ loading: true, error: null }); 10 11 try { 12 // API call 13 const response = await api.get('/domain'); 14 15 // Update state on success 16 set({ 17 items: response.data, 18 loading: false, 19 }); 20 } catch (error) { 21 // Handle error 22 set({ 23 error: error instanceof Error ? error.message : 'Unknown error', 24 loading: false, 25 }); 26 27 // Optionally rethrow for component handling 28 throw error; 29 } 30 }, 31 })) 32);
Optimistic Updates
typescript1updateItem: async (id: string, data: Partial<Item>) => { 2 // Save original state for rollback 3 const originalItems = get().items; 4 5 // Optimistic update 6 set((state) => { 7 const item = state.items.find((i) => i.id === id); 8 if (item) { 9 Object.assign(item, data); 10 } 11 }); 12 13 try { 14 // API call 15 const response = await api.patch(`/domain/${id}`, data); 16 17 // Update with server response 18 set((state) => { 19 const item = state.items.find((i) => i.id === id); 20 if (item) { 21 Object.assign(item, response.data); 22 } 23 }); 24 } catch (error) { 25 // Rollback on error 26 set({ items: originalItems, error: 'Failed to update item' }); 27 throw error; 28 } 29},
Debounced Actions
typescript1import { debounce } from 'lodash-es'; 2 3export const useDomainStore = create<DomainState>()( 4 immer((set, get) => ({ 5 searchQuery: '', 6 searchResults: [], 7 8 // Create debounced function outside of the store 9 searchItems: debounce(async (query: string) => { 10 if (!query) { 11 set({ searchResults: [] }); 12 return; 13 } 14 15 set({ loading: true }); 16 try { 17 const response = await api.get('/domain/search', { params: { q: query } }); 18 set({ searchResults: response.data, loading: false }); 19 } catch (error) { 20 set({ error: 'Search failed', loading: false }); 21 } 22 }, 300), 23 24 setSearchQuery: (query: string) => { 25 set({ searchQuery: query }); 26 get().searchItems(query); // Trigger debounced search 27 }, 28 })) 29);
Persistence
Configure localStorage persistence for user preferences.
Persist Configuration
typescript1import { persist, createJSONStorage } from 'zustand/middleware'; 2 3export const usePreferencesStore = create<PreferencesState>()( 4 persist( 5 immer((set) => ({ 6 theme: 'light', 7 language: 'en', 8 sidebarCollapsed: false, 9 10 setTheme: (theme) => set({ theme }), 11 setLanguage: (language) => set({ language }), 12 toggleSidebar: () => 13 set((state) => { 14 state.sidebarCollapsed = !state.sidebarCollapsed; 15 }), 16 })), 17 { 18 name: 'user-preferences', 19 storage: createJSONStorage(() => localStorage), 20 21 // Only persist specific fields 22 partialPersist: { 23 theme: true, 24 language: true, 25 sidebarCollapsed: true, 26 }, 27 } 28 ) 29);
Session Storage
typescript1import { createJSONStorage } from 'zustand/middleware'; 2 3export const useSessionStore = create<SessionState>()( 4 persist( 5 immer((set) => ({ 6 // Session-only data 7 })), 8 { 9 name: 'session-data', 10 storage: createJSONStorage(() => sessionStorage), // Use sessionStorage 11 } 12 ) 13);
Migration Example
typescript1persist( 2 immer((set) => ({ 3 // Store implementation 4 })), 5 { 6 name: 'domain-storage', 7 version: 2, // Current version 8 9 migrate: (persistedState: any, version: number) => { 10 // Migrate from v0 to v1 11 if (version === 0) { 12 persistedState.newField = 'default'; 13 } 14 15 // Migrate from v1 to v2 16 if (version === 1) { 17 persistedState.renamedField = persistedState.oldField; 18 delete persistedState.oldField; 19 } 20 21 return persistedState; 22 }, 23 } 24);
Testing Stores
Test stores in isolation with mocked dependencies.
Basic Store Test
typescript1// quikadmin-web/src/stores/__tests__/domainStore.test.ts 2import { describe, it, expect, beforeEach, vi } from 'vitest'; 3import { renderHook, act } from '@testing-library/react'; 4import { useDomainStore } from '../domainStore'; 5import * as api from '@/services/api'; 6 7// Mock API 8vi.mock('@/services/api', () => ({ 9 api: { 10 get: vi.fn(), 11 post: vi.fn(), 12 patch: vi.fn(), 13 delete: vi.fn(), 14 }, 15})); 16 17describe('useDomainStore', () => { 18 beforeEach(() => { 19 // Reset store state before each test 20 useDomainStore.setState({ 21 items: [], 22 loading: false, 23 error: null, 24 }); 25 vi.clearAllMocks(); 26 }); 27 28 it('fetches items successfully', async () => { 29 const mockItems = [ 30 { id: '1', name: 'Item 1' }, 31 { id: '2', name: 'Item 2' }, 32 ]; 33 34 vi.mocked(api.api.get).mockResolvedValue({ data: mockItems }); 35 36 const { result } = renderHook(() => useDomainStore()); 37 38 await act(async () => { 39 await result.current.fetchItems(); 40 }); 41 42 expect(result.current.items).toEqual(mockItems); 43 expect(result.current.loading).toBe(false); 44 expect(result.current.error).toBeNull(); 45 }); 46 47 it('handles fetch error', async () => { 48 vi.mocked(api.api.get).mockRejectedValue(new Error('Network error')); 49 50 const { result } = renderHook(() => useDomainStore()); 51 52 await act(async () => { 53 try { 54 await result.current.fetchItems(); 55 } catch (error) { 56 // Expected to throw 57 } 58 }); 59 60 expect(result.current.items).toEqual([]); 61 expect(result.current.loading).toBe(false); 62 expect(result.current.error).toBeTruthy(); 63 }); 64 65 it('creates item', async () => { 66 const newItem = { id: '1', name: 'New Item' }; 67 vi.mocked(api.api.post).mockResolvedValue({ data: newItem }); 68 69 const { result } = renderHook(() => useDomainStore()); 70 71 await act(async () => { 72 await result.current.createItem({ name: 'New Item' }); 73 }); 74 75 expect(result.current.items).toContainEqual(newItem); 76 }); 77});
Testing Selectors
typescript1it('returns filtered items', () => { 2 const { result } = renderHook(() => useDomainStore()); 3 4 act(() => { 5 useDomainStore.setState({ 6 items: [ 7 { id: '1', name: 'Apple' }, 8 { id: '2', name: 'Banana' }, 9 { id: '3', name: 'Apricot' }, 10 ], 11 filter: 'ap', 12 }); 13 }); 14 15 expect(result.current.filteredItems).toHaveLength(2); 16 expect(result.current.filteredItems[0].name).toBe('Apple'); 17 expect(result.current.filteredItems[1].name).toBe('Apricot'); 18});
Best Practices
- Use TypeScript interfaces - Type all store state and actions
- Middleware order matters - devtools → persist → immer
- Use immer for nested updates - Simplifies complex state updates
- Select only what you need - Use selectors to prevent unnecessary re-renders
- Use useShallow for objects - Shallow comparison for multiple values
- Handle loading and errors - Always track async operation states
- Optimize with derived state - Use getters for computed values
- Test stores in isolation - Mock API calls and dependencies
- Persist user preferences only - Don't persist transient data
- Use action naming - Name actions for Redux DevTools
Common Patterns
Store Composition
typescript1// Combine multiple stores in a component 2function Component() { 3 const documents = useDocumentStore((state) => state.documents); 4 const templates = useTemplateStore((state) => state.templates); 5 const { user } = useAuthStore(); 6 7 // Use data from multiple stores 8}
Store Subscription
typescript1import { useEffect } from 'react'; 2 3function Component() { 4 useEffect(() => { 5 // Subscribe to store changes 6 const unsubscribe = useDomainStore.subscribe( 7 (state) => state.items, 8 (items) => { 9 console.log('Items changed:', items); 10 } 11 ); 12 13 return unsubscribe; 14 }, []); 15}
Reset Store
typescript1export const useDomainStore = create<DomainState>()( 2 immer((set) => ({ 3 items: [], 4 loading: false, 5 6 reset: () => { 7 set({ 8 items: [], 9 loading: false, 10 error: null, 11 }); 12 }, 13 })) 14);
References
FAQ & Installation Steps
These questions and steps mirror the structured data on this page for better search understanding.
? Frequently Asked Questions
What is zustand-store?
Perfect for Frontend Agents needing advanced state management capabilities with Zustand stores Intelligent document processing and form automation platform
How do I install zustand-store?
Run the command: npx killer-skills add Intellifill/IntelliFill/zustand-store. It works with Cursor, Windsurf, VS Code, Claude Code, and 19+ other IDEs.
What are the use cases for zustand-store?
Key use cases include: Creating reusable state management patterns, Implementing async actions with middleware, Testing and debugging Zustand stores.
Which IDEs are compatible with zustand-store?
This skill is compatible with Cursor, Windsurf, VS Code, Trae, Claude Code, OpenClaw, Aider, Codex, OpenCode, Goose, Cline, Roo Code, Kiro, Augment Code, Continue, GitHub Copilot, Sourcegraph Cody, and Amazon Q Developer. Use the Killer-Skills CLI for universal one-command installation.
Are there any limitations for zustand-store?
Requires IntelliFill frontend setup. Zustand library dependency. JavaScript expertise needed.
↓ How To Install
-
1. Open your terminal
Open the terminal or command line in your project directory.
-
2. Run the install command
Run: npx killer-skills add Intellifill/IntelliFill/zustand-store. The CLI will automatically detect your IDE or AI agent and configure the skill.
-
3. Start using the skill
The skill is now active. Your AI agent can use zustand-store immediately in the current project.