Skip to main content
Rainbow uses custom store creators built on Zustand for all state management. These replace the dual React Query + Zustand pattern with a unified approach.

Store Creators

Three specialized store creators handle different state patterns:

createRainbowStore

General-purpose store with optional MMKV persistence. Use for client state.

createQueryStore

Data fetching + state in one store. Use for server/async data.

createDerivedStore

Read-only computed state from other stores. Use for aggregated data.

Quick Comparison

FeaturecreateRainbowStorecreateQueryStorecreateDerivedStore
PurposeClient stateServer stateComputed state
PersistenceOptional (MMKV)Optional (MMKV)No
Data FetchingManualBuilt-inN/A
ReactivityStandardReactive paramsAuto-tracking
CachingNoYesNo
MutationsFullFullRead-only

When to Use Each

Use for client-side state that doesn’t require data fetching:
  • UI state (modals, selections, preferences)
  • Form state
  • Local caches
  • Navigation state
  • User preferences
const useUIStore = createRainbowStore((set) => ({
  isModalOpen: false,
  selectedTab: 'wallet',
  setModalOpen: (open: boolean) => set({ isModalOpen: open }),
  setSelectedTab: (tab: string) => set({ selectedTab: tab }),
}));

Basic Usage Pattern

All stores follow a consistent usage pattern:
// Define your store
export const useMyStore = createRainbowStore((set, get) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}));

Store Organization

Stores are organized by domain in src/state/:
state/
├── internal/              # Store creators
│   ├── createRainbowStore.ts
│   ├── createQueryStore.ts
│   └── createDerivedStore.ts
├── wallets/
│   └── walletsStore.ts
├── swaps/
│   └── swapsStore.ts
├── navigation/
│   └── navigationStore.ts
└── nfts/
    └── openCollectionsStore.ts

Real-World Example

Here’s how the navigation store is implemented:
src/state/navigation/navigationStore.ts
import { makeMutable, type SharedValue } from 'react-native-reanimated';
import { type Route } from '@/navigation/Navigation';
import Routes from '@/navigation/routesNames';
import { createRainbowStore } from '../internal/createRainbowStore';

export type NavigationState = {
  activeRoute: Route;
  activeSwipeRoute: SwipeRoute;
  animatedActiveRoute: SharedValue<Route>;
  animatedActiveSwipeRoute: SharedValue<SwipeRoute>;
  isWalletScreenMounted: boolean;
  isRouteActive: (route: Route) => boolean;
  setActiveRoute: (route: Route) => void;
};

export const useNavigationStore = createRainbowStore<NavigationState>((set, get) => ({
  activeRoute: Routes.WALLET_SCREEN,
  activeSwipeRoute: Routes.WALLET_SCREEN,
  animatedActiveRoute: makeMutable<Route>(Routes.WALLET_SCREEN),
  animatedActiveSwipeRoute: makeMutable<SwipeRoute>(Routes.WALLET_SCREEN),
  isWalletScreenMounted: false,

  isRouteActive: route => route === get().activeRoute,

  setActiveRoute: route =>
    set(state => {
      const newActiveRoute = VIRTUAL_NAVIGATORS[route]?.getActiveRoute() ?? route;
      if (newActiveRoute === state.activeRoute) return state;
      
      const onSwipeRoute = isSwipeRoute(newActiveRoute);
      state.animatedActiveRoute.value = newActiveRoute;
      if (onSwipeRoute) state.animatedActiveSwipeRoute.value = newActiveRoute;

      return {
        activeRoute: newActiveRoute,
        activeSwipeRoute: onSwipeRoute ? newActiveRoute : state.activeSwipeRoute,
      };
    }),
}));

// Export methods for use outside React
export const { isRouteActive, setActiveRoute } = useNavigationStore.getState();

Persistence

Stores can optionally persist to MMKV storage:
const useSettingsStore = createRainbowStore(
  (set) => ({
    theme: 'light',
    language: 'en',
    setTheme: (theme) => set({ theme }),
    setLanguage: (language) => set({ language }),
  }),
  {
    storageKey: 'settings',
    version: 1,
  }
);
See Storage for more details on persistence.

Migration from Legacy Systems

React Query and Redux are being phased out. New features should use the custom store creators.

From React Query

import { useQuery } from '@tanstack/react-query';

function useTokenPrice(address: string) {
  return useQuery(['tokenPrice', address], () => fetchPrice(address), {
    staleTime: 5 * 60 * 1000,
  });
}

From Redux

// Slice
const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => { state.value += 1; },
    decrement: (state) => { state.value -= 1; },
  },
});

// Usage
const dispatch = useDispatch();
const value = useSelector((state) => state.counter.value);

External Package Migration

The store creators are being moved to an external stores package:
InternalExternal
createRainbowStorecreateBaseStore
createQueryStorecreateQueryStore
createDerivedStorecreateDerivedStore
The APIs are identical, so migration is just an import swap:
Before
import { createRainbowStore } from '@/state/internal/createRainbowStore';
After
import { createBaseStore } from '@christianbaroni/stores';

Best Practices

Each store should manage a single domain or feature. Don’t create monolithic stores.
// ✅ Good - focused stores
useWalletsStore
useTokensStore
usePricesStore

// ❌ Bad - monolithic store
useAppStore
Always select only the state you need to avoid unnecessary re-renders.
// ✅ Good - selective
const count = useStore((s) => s.count);

// ❌ Bad - entire state
const { count } = useStore();
Export commonly-used methods for use outside React components.
export const useNavStore = createRainbowStore(/* ... */);

// Export for external use
export const { setActiveRoute } = useNavStore.getState();
Always define proper types for your store state.
interface MyStoreState {
  count: number;
  increment: () => void;
}

const useMyStore = createRainbowStore<MyStoreState>(/* ... */);

Next Steps

createRainbowStore

Deep dive into general-purpose stores

createQueryStore

Learn about data fetching stores

createDerivedStore

Understand derived/computed state

Build docs developers (and LLMs) love