Skip to main content

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 in stores/ 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
Profile-scoped stores: Rehydration on profile switch: When switching profiles, all profile-scoped stores are rehydrated from the new profile’s AsyncStorage keys:
This is called in the drawer layout profile switch handler: app/(drawer)/_layout.tsx:105-108

Runtime-Only Stores

Some stores don’t persist to AsyncStorage (in-memory only):
No 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/store/index.ts:1-728

Redux Structure

Migrations

Redux uses redux-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

Avoid adding new features to Redux. Use Zustand instead. Only modify Redux for bug fixes or migrations.

State Migration Strategy

The migration from Redux to Zustand follows this approach:

Phase 1: Settings ✅ Complete

  • Migrated settingsStore to 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-sqlite repository 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-thunk dependencies
  • 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:
  1. Coco SQLite database (coco.db and all coco-N.db files)
  2. All profile-scoped Zustand stores (current profile)
  3. All global Zustand stores
  4. All profile-scoped AsyncStorage keys (all profiles)
  5. Redux persisted state
  6. Secure storage (mnemonics, keys)

Best Practices

Don’t compute values in components - derive them in the store:
Only subscribe to the specific state you need:
Don’t persist everything - only what’s needed:
Always provide error handling for persistence:
Any data specific to a wallet/profile should use profile-scoped storage:

Performance Considerations

Subscription Patterns

Outside React Components

Access store state outside React:

Architecture Overview

See how stores fit into the overall architecture

Cashu Integration

Learn about Coco SQLite storage for proofs and quotes