UNPKG

agent-rules-kit

Version:

CLI tool to bootstrap AI agent rules for multiple IDEs and frameworks. Generates optimized rules for Cursor, VS Code, Claude, and 6+ other AI coding assistants.

626 lines (541 loc) 15.7 kB
--- description: State management overview for React Native applications globs: <root>/src/**/*.ts,<root>/src/**/*.tsx,<root>/src/**/*.js,<root>/src/**/*.jsx alwaysApply: false --- # React Native State Management Overview This guide covers state management patterns and solutions for React Native applications in {projectPath}, including local state, context, Redux Toolkit, and Zustand. ## Local State Management ### useState Hook ```javascript import React, { useState, useCallback } from 'react'; import { View, Text, TextInput, TouchableOpacity } from 'react-native'; export const LoginForm = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const handleLogin = useCallback(async () => { try { setIsLoading(true); setError(null); await authService.login({ email, password }); } catch (err) { setError(err.message); } finally { setIsLoading(false); } }, [email, password]); return ( <View style={styles.container}> <TextInput value={email} onChangeText={setEmail} placeholder="Email" style={styles.input} /> <TextInput value={password} onChangeText={setPassword} placeholder="Password" secureTextEntry style={styles.input} /> {error && <Text style={styles.error}>{error}</Text>} <TouchableOpacity onPress={handleLogin} disabled={isLoading} style={styles.button} > <Text style={styles.buttonText}> {isLoading ? 'Logging in...' : 'Login'} </Text> </TouchableOpacity> </View> ); }; ``` ### useReducer Hook ```javascript import React, { useReducer } from 'react'; // Action types const ACTIONS = { SET_LOADING: 'SET_LOADING', SET_DATA: 'SET_DATA', SET_ERROR: 'SET_ERROR', RESET: 'RESET', }; // Reducer function const dataReducer = (state, action) => { switch (action.type) { case ACTIONS.SET_LOADING: return { ...state, loading: action.payload, error: null }; case ACTIONS.SET_DATA: return { ...state, data: action.payload, loading: false, error: null }; case ACTIONS.SET_ERROR: return { ...state, error: action.payload, loading: false }; case ACTIONS.RESET: return { data: null, loading: false, error: null }; default: return state; } }; // Initial state const initialState = { data: null, loading: false, error: null, }; export const DataFetcher = () => { const [state, dispatch] = useReducer(dataReducer, initialState); const fetchData = useCallback(async () => { dispatch({ type: ACTIONS.SET_LOADING, payload: true }); try { const response = await apiService.getData(); dispatch({ type: ACTIONS.SET_DATA, payload: response }); } catch (error) { dispatch({ type: ACTIONS.SET_ERROR, payload: error.message }); } }, []); return ( <View style={styles.container}> {state.loading && <ActivityIndicator />} {state.error && <Text style={styles.error}>{state.error}</Text>} {state.data && <DataDisplay data={state.data} />} <TouchableOpacity onPress={fetchData}> <Text>Fetch Data</Text> </TouchableOpacity> </View> ); }; ``` ## Context API ### Creating Context ```javascript // contexts/AuthContext.tsx import React, { createContext, useContext, useReducer, useEffect } from 'react'; import AsyncStorage from '@react-native-async-storage/async-storage'; // Types interface User { id: string; email: string; name: string; } interface AuthState { user: User | null; isAuthenticated: boolean; isLoading: boolean; error: string | null; } interface AuthContextType extends AuthState { login: (credentials: LoginCredentials) => Promise<void>; logout: () => Promise<void>; register: (userData: RegisterData) => Promise<void>; } // Action types type AuthAction = | { type: 'SET_LOADING'; payload: boolean } | { type: 'SET_USER'; payload: User } | { type: 'SET_ERROR'; payload: string } | { type: 'LOGOUT' }; // Reducer const authReducer = (state: AuthState, action: AuthAction): AuthState => { switch (action.type) { case 'SET_LOADING': return { ...state, isLoading: action.payload }; case 'SET_USER': return { ...state, user: action.payload, isAuthenticated: true, isLoading: false, error: null, }; case 'SET_ERROR': return { ...state, error: action.payload, isLoading: false, }; case 'LOGOUT': return { user: null, isAuthenticated: false, isLoading: false, error: null, }; default: return state; } }; // Context const AuthContext = createContext<AuthContextType | undefined>(undefined); // Provider export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [state, dispatch] = useReducer(authReducer, { user: null, isAuthenticated: false, isLoading: true, error: null, }); // Initialize auth state useEffect(() => { const initializeAuth = async () => { try { const token = await AsyncStorage.getItem('authToken'); if (token) { const user = await authService.verifyToken(token); dispatch({ type: 'SET_USER', payload: user }); } } catch (error) { console.error('Auth initialization failed:', error); } finally { dispatch({ type: 'SET_LOADING', payload: false }); } }; initializeAuth(); }, []); const login = async (credentials: LoginCredentials) => { dispatch({ type: 'SET_LOADING', payload: true }); try { const response = await authService.login(credentials); await AsyncStorage.setItem('authToken', response.token); dispatch({ type: 'SET_USER', payload: response.user }); } catch (error) { dispatch({ type: 'SET_ERROR', payload: error.message }); } }; const logout = async () => { try { await AsyncStorage.removeItem('authToken'); await authService.logout(); dispatch({ type: 'LOGOUT' }); } catch (error) { console.error('Logout error:', error); } }; const register = async (userData: RegisterData) => { dispatch({ type: 'SET_LOADING', payload: true }); try { const response = await authService.register(userData); await AsyncStorage.setItem('authToken', response.token); dispatch({ type: 'SET_USER', payload: response.user }); } catch (error) { dispatch({ type: 'SET_ERROR', payload: error.message }); } }; return ( <AuthContext.Provider value={{ ...state, login, logout, register }}> {children} </AuthContext.Provider> ); }; // Hook export const useAuth = () => { const context = useContext(AuthContext); if (context === undefined) { throw new Error('useAuth must be used within an AuthProvider'); } return context; }; ``` ## Redux Toolkit ### Store Configuration ```javascript // store/index.ts import { configureStore } from '@reduxjs/toolkit'; import { persistStore, persistReducer } from 'redux-persist'; import AsyncStorage from '@react-native-async-storage/async-storage'; import authSlice from './slices/authSlice'; import userSlice from './slices/userSlice'; import postsSlice from './slices/postsSlice'; // Persist configuration const persistConfig = { key: 'root', storage: AsyncStorage, whitelist: ['auth'], // Only persist auth state }; const persistedAuthReducer = persistReducer(persistConfig, authSlice); export const store = configureStore({ reducer: { auth: persistedAuthReducer, user: userSlice, posts: postsSlice, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'], }, }), }); export const persistor = persistStore(store); export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch; ``` ### Creating Slices ```javascript // store/slices/authSlice.ts import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; // Async thunks export const loginUser = createAsyncThunk( 'auth/loginUser', async (credentials: LoginCredentials, { rejectWithValue }) => { try { const response = await authService.login(credentials); await AsyncStorage.setItem('authToken', response.token); return response; } catch (error) { return rejectWithValue(error.message); } } ); export const logoutUser = createAsyncThunk( 'auth/logoutUser', async (_, { rejectWithValue }) => { try { await AsyncStorage.removeItem('authToken'); await authService.logout(); } catch (error) { return rejectWithValue(error.message); } } ); // Slice const authSlice = createSlice({ name: 'auth', initialState: { user: null, token: null, isAuthenticated: false, isLoading: false, error: null, }, reducers: { clearError: (state) => { state.error = null; }, setUser: (state, action) => { state.user = action.payload; state.isAuthenticated = true; }, }, extraReducers: (builder) => { builder // Login .addCase(loginUser.pending, (state) => { state.isLoading = true; state.error = null; }) .addCase(loginUser.fulfilled, (state, action) => { state.isLoading = false; state.user = action.payload.user; state.token = action.payload.token; state.isAuthenticated = true; }) .addCase(loginUser.rejected, (state, action) => { state.isLoading = false; state.error = action.payload; }) // Logout .addCase(logoutUser.fulfilled, (state) => { state.user = null; state.token = null; state.isAuthenticated = false; }); }, }); export const { clearError, setUser } = authSlice.actions; export default authSlice.reducer; ``` ### Using Redux in Components ```javascript // hooks/redux.ts import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux'; import type { RootState, AppDispatch } from '../store'; export const useAppDispatch = () => useDispatch<AppDispatch>(); export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector; // components/LoginScreen.tsx import { useAppDispatch, useAppSelector } from '../hooks/redux'; import { loginUser, clearError } from '../store/slices/authSlice'; export const LoginScreen = () => { const dispatch = useAppDispatch(); const { isLoading, error } = useAppSelector(state => state.auth); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const handleLogin = () => { dispatch(loginUser({ email, password })); }; useEffect(() => { // Clear error when component unmounts return () => { dispatch(clearError()); }; }, [dispatch]); return ( <View style={styles.container}> <TextInput value={email} onChangeText={setEmail} placeholder="Email" /> <TextInput value={password} onChangeText={setPassword} placeholder="Password" secureTextEntry /> {error && <Text style={styles.error}>{error}</Text>} <TouchableOpacity onPress={handleLogin} disabled={isLoading}> <Text>{isLoading ? 'Logging in...' : 'Login'}</Text> </TouchableOpacity> </View> ); }; ``` ## Zustand ### Store Setup ```javascript // store/useAuthStore.ts import { create } from 'zustand'; import { persist, createJSONStorage } from 'zustand/middleware'; import AsyncStorage from '@react-native-async-storage/async-storage'; interface User { id: string; email: string; name: string; } interface AuthState { user: User | null; token: string | null; isAuthenticated: boolean; isLoading: boolean; error: string | null; } interface AuthActions { login: (credentials: LoginCredentials) => Promise<void>; logout: () => Promise<void>; setUser: (user: User) => void; clearError: () => void; } export const useAuthStore = create<AuthState & AuthActions>()( persist( (set, get) => ({ // State user: null, token: null, isAuthenticated: false, isLoading: false, error: null, // Actions login: async (credentials) => { set({ isLoading: true, error: null }); try { const response = await authService.login(credentials); await AsyncStorage.setItem('authToken', response.token); set({ user: response.user, token: response.token, isAuthenticated: true, isLoading: false, }); } catch (error) { set({ error: error.message, isLoading: false }); } }, logout: async () => { try { await AsyncStorage.removeItem('authToken'); await authService.logout(); set({ user: null, token: null, isAuthenticated: false, error: null, }); } catch (error) { console.error('Logout error:', error); } }, setUser: (user) => set({ user, isAuthenticated: true }), clearError: () => set({ error: null }), }), { name: 'auth-storage', storage: createJSONStorage(() => AsyncStorage), partialize: (state) => ({ user: state.user, token: state.token, isAuthenticated: state.isAuthenticated, }), } ) ); ``` ### Using Zustand in Components ```javascript // components/ProfileScreen.tsx import { useAuthStore } from '../store/useAuthStore'; export const ProfileScreen = () => { const { user, logout, isLoading } = useAuthStore(); const handleLogout = () => { logout(); }; if (!user) { return <Text>No user data</Text>; } return ( <View style={styles.container}> <Text style={styles.name}>{user.name}</Text> <Text style={styles.email}>{user.email}</Text> <TouchableOpacity onPress={handleLogout} disabled={isLoading}> <Text>{isLoading ? 'Logging out...' : 'Logout'}</Text> </TouchableOpacity> </View> ); }; ``` ## State Management Best Practices ### When to Use Each Solution 1. **Local State (useState/useReducer)**: - Component-specific state - Form inputs - UI state (modals, toggles) - Simple data fetching 2. **Context API**: - Theme management - User authentication - App-wide settings - Small to medium apps 3. **Redux Toolkit**: - Complex state logic - Large applications - Time-travel debugging needed - Multiple data sources 4. **Zustand**: - Simpler alternative to Redux - TypeScript-first approach - Minimal boilerplate - Medium to large apps ### Performance Optimization ```javascript // Memoization with React.memo export const UserCard = React.memo(({ user, onPress }) => { return ( <TouchableOpacity onPress={() => onPress(user.id)}> <Text>{user.name}</Text> </TouchableOpacity> ); }); // Selector optimization const useOptimizedSelector = () => { return useAppSelector( (state) => ({ user: state.auth.user, isLoading: state.auth.isLoading, }), (left, right) => left.user?.id === right.user?.id && left.isLoading === right.isLoading ); }; ``` This overview provides comprehensive guidance for choosing and implementing state management solutions in React Native applications.