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
Feature createRainbowStore createQueryStore createDerivedStore Purpose Client state Server state Computed state Persistence Optional (MMKV) Optional (MMKV) No Data Fetching Manual Built-in N/A Reactivity Standard Reactive params Auto-tracking Caching No Yes No Mutations Full Full Read-only
When to Use Each
createRainbowStore
createQueryStore
createDerivedStore
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 }),
}));
Use for server state or any async data:
API responses
Blockchain data
User balances
Token prices
NFT metadata
const useTokenPriceStore = createQueryStore ({
fetcher : async ({ tokenAddress }) => {
return await fetchTokenPrice ( tokenAddress );
},
params: {
tokenAddress: '0x...' ,
},
staleTime: time . minutes ( 5 ),
});
Use for computed state from other stores:
Portfolio totals
Filtered lists
Aggregated metrics
Cross-store calculations
const usePortfolioTotal = createDerivedStore (( $ ) => {
const tokens = $ ( useTokensStore ). tokens ;
const prices = $ ( usePricesStore ). prices ;
return tokens . reduce (( total , token ) => {
return total + token . balance * prices [ token . address ];
}, 0 );
});
Basic Usage Pattern
All stores follow a consistent usage pattern:
Creating a Store
Using in Components
Outside React
// 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
Before (React Query)
After (createQueryStore)
import { useQuery } from '@tanstack/react-query' ;
function useTokenPrice ( address : string ) {
return useQuery ([ 'tokenPrice' , address ], () => fetchPrice ( address ), {
staleTime: 5 * 60 * 1000 ,
});
}
From Redux
Before (Redux)
After (createRainbowStore)
// 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:
Internal External createRainbowStorecreateBaseStorecreateQueryStorecreateQueryStorecreateDerivedStorecreateDerivedStore
The APIs are identical, so migration is just an import swap:
import { createRainbowStore } from '@/state/internal/createRainbowStore' ;
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
Use selectors for performance
Export methods for external use
Export commonly-used methods for use outside React components. export const useNavStore = createRainbowStore ( /* ... */ );
// Export for external use
export const { setActiveRoute } = useNavStore . getState ();
Use TypeScript for type safety
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