State Management Overview
Sovran is transitioning from Redux to Zustand for state management. The migration is ongoing, with new features using Zustand while legacy code still uses Redux.Why Zustand?
- Simpler API: Less boilerplate than Redux (no actions, reducers, or dispatch)
- Better TypeScript: Full type inference without manual typing
- Smaller bundle: ~1KB vs Redux’s ~10KB
- React-first: Hooks-based API that feels native to React
- Profile-scoped stores: Easy to create per-profile state isolation
Zustand Stores
All Zustand stores live instores/ and follow a consistent pattern.
Store Architecture
stores/settingsStore.ts:144-267 Store pattern:Global Stores
These stores persist globally across all profiles:Profile-Scoped Stores
These stores are isolated per profile (wallet). Each profile has its own AsyncStorage key:- Account 0:
mint-store-profile-0 - Account 1:
mint-store-profile-1 - Account N:
mint-store-profile-N
Rehydration on profile switch:
When switching profiles, all profile-scoped stores are rehydrated from the new profile’s AsyncStorage keys:
Runtime-Only Stores
Some stores don’t persist to AsyncStorage (in-memory only):persist() middleware means state resets on app restart.
Redux (Legacy)
Redux is being phased out but still handles:- Cashu proof state (migrating to Coco SQLite)
- Nostr contact state (migrating to Zustand)
- Settings (already migrated to
settingsStore)
Redux Structure
Migrations
Redux usesredux-persist migrations to evolve the store schema:
redux/store/index.ts:17-587
Migration 151 moves the mnemonic from Redux to expo-secure-store:
redux/store/index.ts:506-559
Migration 152 migrates settings from Redux to Zustand:
redux/store/index.ts:560-586
Accessing Redux State
State Migration Strategy
The migration from Redux to Zustand follows this approach:Phase 1: Settings ✅ Complete
- Migrated
settingsStoreto Zustand - Migration runs automatically on app startup (see migration 152)
- Old Redux settings slice is now ignored
Phase 2: Cashu State 🚧 In Progress
- Moving from Redux to Coco SQLite (not Zustand)
- Proofs, quotes, and transactions now live in
coco.db - Redux cashu slice will be removed once migration is complete
- Uses
coco-cashu-expo-sqliterepository layer
Phase 3: Nostr State 📋 Planned
- Migrate contacts/profiles to Zustand stores
- Use NDK’s SQLite cache for event data
- Keep Redux only for backward compatibility during migration
Phase 4: Complete Removal 🎯 Future
- Remove Redux entirely
- Delete
redux/directory - Remove
redux,react-redux,redux-persist,redux-thunkdependencies - Estimated bundle size reduction: ~15KB
Store Clearing on Reset
When the user deletes all data (Settings → Delete Everything), all stores are cleared: redux/store/index.ts:608-727 This clears:- Coco SQLite database (
coco.dband allcoco-N.dbfiles) - All profile-scoped Zustand stores (current profile)
- All global Zustand stores
- All profile-scoped AsyncStorage keys (all profiles)
- Redux persisted state
- Secure storage (mnemonics, keys)
Best Practices
Use selectors for derived state
Use selectors for derived state
Don’t compute values in components - derive them in the store:
Minimize re-renders with selectors
Minimize re-renders with selectors
Only subscribe to the specific state you need:
Use partialize to control persistence
Use partialize to control persistence
Don’t persist everything - only what’s needed:
Handle rehydration errors
Handle rehydration errors
Always provide error handling for persistence:
Use profile-scoped stores for user data
Use profile-scoped stores for user data
Any data specific to a wallet/profile should use profile-scoped storage:
Performance Considerations
Subscription Patterns
Outside React Components
Access store state outside React:Related Documentation
Architecture Overview
See how stores fit into the overall architecture
Cashu Integration
Learn about Coco SQLite storage for proofs and quotes