Skip to main content

react-native-state-management

Manages application state in React Native using Context API, Zustand, Redux Toolkit, and Jotai with proper persistence, middleware, and re-render optimization for mobile.

Jump to install

Source facts

Repository
paulpas/agent-skill-router
Last source activity
June 9, 2026 at 16:28
Detected SKILL.md language
English
Stars
6
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
react-native-state-management
description
Manages application state in React Native using Context API, Zustand, Redux Toolkit, and Jotai with proper persistence, middleware, and re-render optimization for mobile.
license
MIT
compatibility
opencode
archetypes
["tactical","generation"]
anti_triggers
["brainstorming","vague ideation","long-form architecture"]
response_profile
{"verbosity":"low","directive_strength":"high","abstraction_level":"operational"}
metadata
{"version":"1.0.0","domain":"coding","triggers":"react native state, state management, zustand, redux toolkit, jotai, asyncstorage, react native data","role":"implementation","scope":"implementation","output-format":"code","content-types":["code","guidance","do-dont","examples"],"related-skills":"react-native-rendering, react-native-monorepo-config","author":"https://github.com/vercel-labs","source":"https://github.com/vercel-labs/agent-skills"}
# React Native State Management A senior React Native engineer selecting and implementing state management solutions (Context API, Zustand, Redux Toolkit, Jotai) with persistence, middleware, and performance optimization for mobile applications. ## TL;DR Checklist - [ ] Evaluate app complexity before choosing a state management library - [ ] Use Zustand or Jotai for medium-complexity apps (minimal boilerplate, great DX) - [ ] Use Redux Toolkit with RTK Query for complex apps needing API caching and devtools - [ ] Implement state persistence with AsyncStorage + middleware layer - [ ] Use selectors with equality checks (useShallow, shallow) to prevent re-renders - [ ] Code-split stores by feature domain for large applications - [ ] Reset all state on auth logout via a single action - [ ] Keep state flat — never store derived/computed data --- ## When to Use Use this skill when: - Building a new React Native app and deciding on a state management strategy - Refactoring a component that re-renders too often due to global state subscriptions - Implementing offline-first behavior with persisted state - Adding auth flows that require state reset on logout - Setting up Redux Toolkit with RTK Query for API caching - Introducing Zustand as a lightweight alternative to Redux --- ## When NOT to Use Avoid this skill for: - Simple apps with minimal shared state — React's built-in `useState` + prop drilling is sufficient - Web-only React projects (no AsyncStorage dependency) — use localStorage or similar instead - Server-state dominated apps where React Query or SWR alone suffices (no client state needed) - Performance-critical animations driven by state — use Reanimated shared values instead --- ## Core Workflow 1. **Assess App Complexity** — Determine the right approach: - **Simple** (1-2 shared values) → Context API + `useReducer` - **Medium** (multi-feature, moderate state) → Zustand or Jotai - **Complex** (many features, API caching, middleware) → Redux Toolkit + RTK Query **Checkpoint:** If you need API caching, choose RTK Query or React Query before adding a state library. 2. **Define State Shape** — Design flat, normalized state slices: - Keep state as flat as possible — avoid deeply nested objects - Never store derived data (filtered lists, computed totals) — compute with selectors - Separate server state (API data) from client state (UI toggles, form inputs) **Checkpoint:** Can every piece of state be serialized to JSON? If not, move non-serializable data to a ref or middleware. 3. **Implement Store with Selectors** — Create the store/slice with memoized selectors: - Use `createSelector` (Redux) or `useShallow` (Zustand) for equality checks - Code-split by feature — one store/slice per app domain - Keep actions and reducers colocated with their feature **Checkpoint:** Verify that components only re-render when their specific slice of state changes. 4. **Add Persistence** — Wire AsyncStorage with the appropriate middleware: - Redux: `redux-persist` with `AsyncStorage` engine - Zustand: `persist` middleware - Jotai: `atomWithStorage` from `jotai/utils` - Only persist essential user data (auth tokens, preferences, drafts) — never cache full API responses **Checkpoint:** Test that state survives app kill/restart on both iOS and Android. 5. **Handle Auth Flow** — Implement login/logout with proper state lifecycle: - On login: restore persisted state or initialize fresh - On logout: dispatch a single `RESET_STATE` action or call `store.reset()` - Never manually clear individual slices — one atomic reset prevents stale data bugs **Checkpoint:** Verify that after logout, no previous user's data is accessible. --- ## Implementation Patterns ### Pattern 1: Zustand Store with Persistence (Medium Complexity) ```tsx import { create } from 'zustand'; import { persist, createJSONStorage } from 'zustand/middleware'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { useShallow } from 'zustand/react/shallow'; // --- State shape --- interface UserPreferences { theme: 'light' | 'dark' | 'system'; fontSize: number; notificationsEnabled: boolean; } interface AppState { // Auth userId: string | null; token: string | null; // Preferences (persisted) preferences: UserPreferences; // Actions login: (userId: string, token: string) => void; logout: () => void; updatePreferences: (prefs: Partial<UserPreferences>) => void; } const initialState = { userId: null, token: null, preferences: { theme: 'system' as const, fontSize: 16, notificationsEnabled: true, }, }; // --- Zustand store with persistence --- export const useAppStore = create<AppState>()( persist( (set) => ({ ...initialState, login: (userId, token) => set({ userId, token }), logout: () => set(initialState), // Single atomic reset updatePreferences: (prefs) => set((state) => ({ preferences: { ...state.preferences, ...prefs }, })), }), { name: 'app-state', storage: createJSONStorage(() => AsyncStorage), // Only persist auth tokens and preferences — skip transient UI state partialize: (state) => ({ userId: state.userId, token: state.token, preferences: state.preferences, }), } ) ); // --- Selector with shallow comparison prevents unnecessary re-renders --- export function useTheme() { return useAppStore(useShallow((state) => ({ theme: state.preferences.theme, fontSize: state.preferences.fontSize, }))); } ``` ### Pattern 2: Redux Toolkit with RTK Query (Complex App) ```tsx import { configureStore, createSlice, createSelector, PayloadAction } from '@reduxjs/toolkit'; import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; import { persistStore, persistReducer, FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER, } from 'redux-persist'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; // --- API slice (server state) --- interface Post { id: number; title: string; body: string; } export const postsApi = createApi({ reducerPath: 'postsApi', baseQuery: fetchBaseQuery({ baseUrl: 'https://jsonplaceholder.typicode.com' }), endpoints: (builder) => ({ getPosts: builder.query<Post[], void>({ query: () => '/posts', }), getPost: builder.query<Post, number>({ query: (id) => `/posts/${id}`, }), }), }); export const { useGetPostsQuery, useGetPostQuery } = postsApi; // --- Client state slice (UI-only state) --- interface UIState { selectedPostId: number | null; isGridView: boolean; } const uiSlice = createSlice({ name: 'ui', initialState: { selectedPostId: null, isGridView: false, } as UIState, reducers: { selectPost(state, action: PayloadAction<number | null>) { state.selectedPostId = action.payload; }, toggleView(state) { state.isGridView = !state.isGridView; }, }, }); export const { selectPost, toggleView } = uiSlice.actions; // --- Memoized selector --- const selectIsGridView = (state: RootState) => state.ui.isGridView; export const selectLayoutMode = createSelector( [selectIsGridView], (isGridView) => (isGridView ? 'grid' : 'list' as const) ); // --- Persist only UI slice (RTK Query manages its own cache) --- const persistConfig = { key: 'ui', storage: AsyncStorage, whitelist: ['isGridView'], // Don't persist selectedPostId — it's transient }; const persistedUIReducer = persistReducer(persistConfig, uiSlice.reducer); // --- Store --- export const store = configureStore({ reducer: { [postsApi.reducerPath]: postsApi.reducer, ui: persistedUIReducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], }, }).concat(postsApi.middleware), }); export const persistor = persistStore(store); export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch; export const useAppDispatch: () => AppDispatch = useDispatch; export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector; ``` ### Pattern 3: Jotai for Fine-Grained Atomic State ```tsx import { atom, useAtom, useAtomValue } from 'jotai'; import { atomWithStorage, splitAtom } from 'jotai/utils'; import AsyncStorage from '@react-native-async-storage/async-storage'; // --- Persisted atoms --- export const themeAtom = atomWithStorage<'light' | 'dark'>( 'theme', 'light', { getItem: async (key) => { const value = await AsyncStorage.getItem(key); return value === 'dark' ? 'dark' : 'light'; }, setItem: async (key, value) => { await AsyncStorage.setItem(key, value); }, removeItem: async (key) => { await AsyncStorage.removeItem(key); }, } ); // --- Derived atom (no derived data stored in state) --- export const isDarkAtom = atom((get) => get(themeAtom) === 'dark'); // --- Atom family (splitAtom for dynamic lists) --- interface TodoItem { id: string; text: string; completed: boolean; } export const todosAtom = atom<TodoItem[]>([]); export const todoAtomsAtom = splitAtom(todosAtom); // --- Computed via selectors, not stored --- export const completedTodosAtom = atom((get) => get(todosAtom).filter((todo) => todo.completed) ); export const pendingTodosAtom = atom((get) => get(todosAtom).filter((todo) => !todo.completed) ); // --- Component usage (only re-renders when specific atom changes) --- function ThemeToggle() { const [theme, setTheme] = useAtom(themeAtom); const isDark = useAtomValue(isDarkAtom); return ( <button onPress={() => setTheme(theme === 'light' ? 'dark' : 'light')}> Current: {theme} {isDark ? '🌙' : '☀️'} </button> ); } ``` ### Pattern 4: Context API Pitfall — When Not to Use It ```tsx // ❌ BAD: Context for rapidly-updating global state (causes full-tree re-renders) const PositionContext = createContext({ latitude: 0, longitude: 0, heading: 0, speed: 0, }); function PositionProvider({ children }: { children: React.ReactNode }) { const [position, setPosition] = useState({ latitude: 0, longitude: 0, heading: 0, speed: 0 }); useEffect(() => { const sub = Location.watchPositionAsync( { enableHighAccuracy: true, timeInterval: 100 }, (loc) => setPosition({ latitude: loc.coords.latitude, longitude: loc.coords.longitude, heading: loc.coords.heading ?? 0, speed: loc.coords.speed ?? 0, }) ); return () => sub.then((s) => s.remove()); }, []); return <PositionContext.Provider value={position}>{children}</PositionContext.Provider>; } // Every consumer re-renders 10 times/second — even those that only show heading! // ✅ GOOD: Use Zustand for high-frequency updates (only subscribed components re-render) import { create } from 'zustand'; interface PositionState { latitude: number; longitude: number; heading: number; speed: number; setPosition: (pos: Omit<PositionState, 'setPosition'>) => void; } export const usePositionStore = create<PositionState>((set) => ({ latitude: 0, longitude: 0, heading: 0, speed: 0, setPosition: (pos) => set(pos), })); // Only this component re-renders on each GPS update function HeadingDisplay() { const heading = usePositionStore((s) => s.heading); // Subscribes only to `heading` return <Text>Heading: {heading}°</Text>; } ``` --- ## Constraints ### MUST DO - Use selectors with equality checks (useShallow, shallow, createSelector) to prevent unnecessary re-renders - Implement state persistence via AsyncStorage for user preferences, auth tokens, and draft data - Handle auth flow state reset atomically — one action clears everything - Code-split stores by feature domain (auth/, feed/, settings/) in large apps - Keep state serializable at all times — non-serializable data belongs in refs or middleware - Store only the minimum necessary state — derive everything else via selectors ### MUST NOT DO - Store derived/computed data in state — compute with selectors instead - Use Context API for high-frequency global state updates (GPS, websocket data, animation values) - Store full API responses in persisted state — use RTK Query cache or React Query instead - Mix server state (API data) and client state (UI toggles) in the same slice - Reset state by manually clearing individual keys — always use an atomic reset action - Use mutable patterns in Redux Toolkit (the immer-based reducer handles immutability for you) --- ## Related Skills | Skill | Purpose | |---|---| | `react-native-rendering` | Optimizes component rendering in tandem with state management | | `react-native-monorepo-config` | Configures monorepo workspaces for sharing state libraries across packages | --- ## Live References > Authoritative documentation links for React Native state management. - [React Native State Management Overview](https://reactnative.dev/docs/state) — Official React Native guide - [Zustand Documentation](https://github.com/pmndrs/zustand) — Lightweight state management for React - [Redux Toolkit Docs](https://redux-toolkit.js.org/) — Official Redux Toolkit with RTK Query - [Jotai Documentation](https://jotai.org/docs) — Atomic state management for React - [Redux Persist](https://github.com/rt2zz/redux-persist) — Persist and rehydrate Redux state
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub