Frontend State Management Overview
Implement scalable state management solutions using modern patterns and libraries to handle application state, side effects, and data flow across components.
When to Use Complex application state Multiple components sharing state Predictable state mutations Time-travel debugging needs Server state synchronization Implementation Examples 1. Redux with Redux Toolkit (React) // store/userSlice.ts import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
interface User { id: number; name: string; email: string; }
interface UserState { items: User[]; loading: boolean; error: string | null; }
const initialState: UserState = { items: [], loading: false, error: null };
export const fetchUsers = createAsyncThunk( 'users/fetchUsers', async (_, { rejectWithValue }) => { try { const response = await fetch('/api/users'); if (!response.ok) throw new Error('Failed to fetch'); return await response.json(); } catch (error) { return rejectWithValue((error as Error).message); } } );
const userSlice = createSlice({
name: 'users',
initialState,
reducers: {
userAdded(state, action: PayloadAction
export const { userAdded, userRemoved } = userSlice.actions; export default userSlice.reducer;
// store/index.ts import { configureStore } from '@reduxjs/toolkit'; import userReducer from './userSlice';
export const store = configureStore({ reducer: { users: userReducer } });
export type RootState = ReturnType
// Usage in component import { useDispatch, useSelector } from 'react-redux';
const UsersList: React.FC = () => {
const dispatch = useDispatch
React.useEffect(() => { dispatch(fetchUsers()); }, [dispatch]);
if (loading) return
Loading...
; if (error) returnError: {error}
;return (
-
{items.map(user => (
- {user.name} ))}
- Zustand (Lightweight State Management) // store/useUserStore.ts import create from 'zustand';
interface User { id: number; name: string; email: string; }
interface UserStore {
users: User[];
loading: boolean;
error: string | null;
fetchUsers: () => Promise
export const useUserStore = create
fetchUsers: async () => { set({ loading: true, error: null }); try { const response = await fetch('/api/users'); if (!response.ok) throw new Error('Failed to fetch'); const users = await response.json(); set({ users, loading: false }); } catch (error) { set({ error: (error as Error).message, loading: false }); } },
addUser: (user) => set((state) => ({ users: [...state.users, user] })),
removeUser: (id) => set((state) => ({ users: state.users.filter(u => u.id !== id) })),
clearError: () => set({ error: null }) }));
// Usage in component const UsersList: React.FC = () => { const { users, loading, error, fetchUsers } = useUserStore();
React.useEffect(() => { fetchUsers(); }, [fetchUsers]);
if (loading) return
Loading...
; if (error) returnError: {error}
;return (
-
{users.map(user => (
- {user.name} ))}
- Context API + useReducer // context/AuthContext.tsx import React, { createContext, useReducer, useCallback } from 'react';
interface User { id: number; name: string; email: string; }
interface AuthState { user: User | null; loading: boolean; error: string | null; isAuthenticated: boolean; }
type AuthAction = | { type: 'LOGIN_START' } | { type: 'LOGIN_SUCCESS'; payload: User } | { type: 'LOGIN_ERROR'; payload: string } | { type: 'LOGOUT' };
const initialState: AuthState = { user: null, loading: false, error: null, isAuthenticated: false };
function authReducer(state: AuthState, action: AuthAction): AuthState { switch (action.type) { case 'LOGIN_START': return { ...state, loading: true, error: null }; case 'LOGIN_SUCCESS': return { ...state, user: action.payload, loading: false, isAuthenticated: true }; case 'LOGIN_ERROR': return { ...state, error: action.payload, loading: false }; case 'LOGOUT': return { ...state, user: null, isAuthenticated: false }; default: return state; } }
interface AuthContextType {
state: AuthState;
login: (email: string, password: string) => Promise
export const AuthContext = createContext
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [state, dispatch] = useReducer(authReducer, initialState);
const login = useCallback(async (email: string, password: string) => { dispatch({ type: 'LOGIN_START' }); try { const response = await fetch('/api/login', { method: 'POST', body: JSON.stringify({ email, password }) }); if (!response.ok) throw new Error('Login failed'); const user = await response.json(); dispatch({ type: 'LOGIN_SUCCESS', payload: user }); } catch (error) { dispatch({ type: 'LOGIN_ERROR', payload: (error as Error).message }); } }, []);
const logout = useCallback(() => { dispatch({ type: 'LOGOUT' }); }, []);
return (
export const useAuth = () => { const context = React.useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within AuthProvider'); } return context; };
- MobX (Observable State) // store/UserStore.ts import { makeObservable, observable, action, runInAction } from 'mobx';
interface User { id: number; name: string; email: string; }
class UserStore { users: User[] = []; loading = false; error: string | null = null;
constructor() { makeObservable(this, { users: observable, loading: observable, error: observable, fetchUsers: action, addUser: action, removeUser: action, clearError: action }); }
async fetchUsers() { this.loading = true; this.error = null;
try {
const response = await fetch('/api/users');
if (!response.ok) throw new Error('Failed to fetch');
const data = await response.json();
runInAction(() => {
this.users = data;
this.loading = false;
});
} catch (error) {
runInAction(() => {
this.error = (error as Error).message;
this.loading = false;
});
}
}
addUser(user: User) { this.users.push(user); }
removeUser(id: number) { this.users = this.users.filter(u => u.id !== id); }
clearError() { this.error = null; } }
export const userStore = new UserStore();
// Usage with React import { observer } from 'mobx-react-lite';
const UsersList = observer(() => { const { users, loading, error, fetchUsers } = userStore;
React.useEffect(() => { fetchUsers(); }, []);
if (loading) return
Loading...
; if (error) returnError: {error}
;return (
-
{users.map(user => (
- {user.name} ))}
Best Practices Choose state management based on app complexity Keep state normalized and flat Separate application and UI state Implement proper error handling Use selectors to derive data Implement middleware for side effects Monitor performance and bundle size Document state shape and actions Resources Redux Documentation Zustand MobX Context API Pinia (Vue)