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
Markdown
---
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.