Skill: ahooks
Scope
- Applies to: ahooks v3+ utility hooks for React state management, grouped state updates, localStorage persistence
- Does NOT cover: Data fetching (use TanStack Query), URL state management (use nuqs), complex state machines, async operations
Assumptions
- ahooks v3+
- React 18+ with hooks support
- TypeScript v5+ (for type inference)
- Client-side only (hooks require browser APIs for localStorage)
Principles
- Use
useSetStatefor grouped state that changes together (not URL-shareable) - Use
useLocalStorageStatefor persistent state across browser sessions - Prefer nuqs for URL-shareable state over localStorage
- Prefer TanStack Query for async operations and loading states
- Use
useStateonly for simple independent state (rare)
Constraints
MUST
- Use
useSetStatefor grouped state not in URL (form state, game engine state, ephemeral UI state) - Use
useLocalStorageStatefor localStorage persistence
SHOULD
- Prefer nuqs for URL-shareable state over localStorage
- Prefer TanStack Query for async operations and loading states
- Use TypeScript generics for type-safe state:
useLocalStorageState<boolean>('key', { defaultValue: false })
AVOID
- Using for URL state (use nuqs instead)
- Using for loading/error states (use TanStack Query instead)
- Using for complex async operations (use TanStack Query instead)
- Mixing URL state with localStorage without explicit sync logic
Interactions
- Complements nuqs for URL state management (can sync bidirectionally)
- Complements TanStack Query for async operations (use TanStack Query for data fetching)
- Part of state management decision tree (see React rules)
- Works with React 18+ hooks architecture
Patterns
useSetState Pattern
Use for grouped state that updates together:
typescript1import { useSetState } from 'ahooks' 2 3const [state, setState] = useSetState({ 4 name: '', 5 email: '', 6 age: 0, 7}) 8 9// Partial updates (shallow merge) 10setState({ name: 'John' }) 11setState(prev => ({ ...prev, email: 'john@example.com' }))
When to use: Form state, game engine state, UI state that changes together (if not URL-shareable)
When NOT to use: URL-shareable state (use nuqs), loading states (use TanStack Query)
useLocalStorageState Pattern
Use for state that persists across browser sessions:
typescript1import { useLocalStorageState } from 'ahooks' 2 3const [value, setValue] = useLocalStorageState<boolean>('key', { 4 defaultValue: false, 5}) 6 7// Type-safe with generics 8const [settings, setSettings] = useLocalStorageState<Settings>('settings', { 9 defaultValue: { theme: 'light', fontSize: 14 }, 10})
When to use: User preferences, debug flags, settings that should persist across sessions
When NOT to use: URL-shareable state (use nuqs), sensitive data (use secure storage)
Integration with nuqs
Bidirectional sync between URL and localStorage:
typescript1import { useLocalStorageState } from 'ahooks' 2import { useQueryState } from 'nuqs' 3import { useEffect, useRef } from 'react' 4 5const [queryState, setQueryState] = useQueryState('debug') 6const [storageState, setStorageState] = useLocalStorageState<boolean>('debug', { 7 defaultValue: false, 8}) 9const isFirstMount = useRef(true) 10 11// Sync on mount: localStorage → URL 12useEffect(() => { 13 if (isFirstMount.current) { 14 isFirstMount.current = false 15 if (storageState && queryState !== 'true') { 16 setQueryState('true') 17 return 18 } 19 } 20 // Sync changes: URL → localStorage 21 if (queryState === 'true' && !storageState) { 22 setStorageState(true) 23 } else if (queryState === 'false' && storageState) { 24 setStorageState(false) 25 } 26}, [queryState, storageState, setQueryState, setStorageState])
Use case: Debug flags, feature toggles that should be both URL-shareable and persistent
State Management Decision Tree
- URL-shareable state → Use
nuqs(filters, search, tabs, pagination) - Grouped state not in URL → Use
useSetState(form state, game engine, ephemeral UI) - Async operations → Use TanStack Query (data fetching, mutations, caching)
- localStorage persistence → Use
useLocalStorageState(preferences, settings) - Simple independent state → Use
useState(rare, prefer other options)
References
- ahooks documentation - Official documentation
- React rules - State management decision tree and patterns
- TanStack Query - Async operations and data fetching patterns