| name | data-and-state-management |
| description | Core patterns for data fetching, state management, and user preferences. Use when implementing new features that require getting data from Our APIs, Morpho API, on-chain states or managing shared state. |
Quick Reference
State Management Decision Tree
External Data (API, blockchain) → React Query
User Preferences (persist across refresh) → Zustand + persist
Shared UI State (modals, selections, operations) → Zustand
Computed/Derived → useMemo Hook
Local UI State (single component) → useState
Detailed Patterns
Data Flow
1. Try Morpho API (if network supported)
↓ fails
2. Fallback to The Graph Subgraph
↓ optional
3. Enhance with on-chain RPC data
React Query (External Data)
Location: src/hooks/queries/use{Entity}Query.ts
export const useMarketsQuery = () => {
return useQuery({
queryKey: ['markets'],
queryFn: fetchMarkets,
staleTime: 5 * 60 * 1000,
});
};
// Usage
const { data, isLoading } = useMarketsQuery();
Zustand + Persist (User Preferences)
Location: src/stores/use{Feature}{State}.ts
export const useMarketsFilters = create(
persist(
(set) => ({
selectedNetwork: null,
setSelectedNetwork: (network) => set({ selectedNetwork: network }),
}),
{ name: 'monarch_store_marketsFilters' }
)
);
// Usage - separate selectors for primitives
const network = useMarketsFilters((s) => s.selectedNetwork);
const setNetwork = useMarketsFilters((s) => s.setSelectedNetwork);
Zustand (Shared UI State)
Location: src/stores/use{Feature}Store.ts
// Modal state
export const useVaultModalStore = create((set) => ({
isOpen: false,
open: () => set({ isOpen: true }),
close: () => set({ isOpen: false }),
}));
// Selection state
export const useTableSelectionStore = create((set) => ({
selectedIds: [],
toggleSelection: (id) => set((state) => ({
selectedIds: state.selectedIds.includes(id)
? state.selectedIds.filter((i) => i !== id)
: [...state.selectedIds, id]
})),
clearSelection: () => set({ selectedIds: [] }),
}));
Derived Data Hooks
Location: src/hooks/use{Processed|Filtered}{Entity}.ts
export const useFilteredMarkets = () => {
const { data } = useMarketsQuery();
const searchQuery = useMarketsFilters((s) => s.searchQuery);
return useMemo(() => {
return data
.filter((m) => m.symbol.includes(searchQuery))
.sort((a, b) => b.tvl - a.tvl);
}, [data, searchQuery]);
};
Anti-Patterns
// ❌ Don't fetch in Context
const Provider = () => {
useEffect(() => { fetch().then(setData); }, []);
return <Context.Provider value={data}>{children}</Context.Provider>;
};
// ✅ Use React Query
const useDataQuery = () => useQuery({ queryKey: ['data'], queryFn: fetch });
// ❌ Don't create objects in selectors (infinite loop)
const filters = useStore((s) => s.filters ?? { min: '0' });
// ✅ Return primitives
const min = useStore((s) => s.filters?.min ?? '0');
// ❌ Don't use useState for shared state
const [modalOpen, setModalOpen] = useState(false);
// Then pass through 3 levels of props...
// ✅ Use Zustand for shared UI state
const useModalStore = create((set) => ({
isOpen: false,
open: () => set({ isOpen: true }),
}));
// ❌ Don't chain useEffect
useEffect(() => { setFiltered(data.filter(...)); }, [data]);
useEffect(() => { setSorted(filtered.sort(...)); }, [filtered]);
// ✅ Use useMemo
const processed = useMemo(() => data.filter(...).sort(...), [data]);