State Management
3 min readReact State Management
Comprehensive guide to state management patterns in React applications.
Local Component State
// Simple local state
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Lifting State Up
function Parent() {
const [sharedValue, setSharedValue] = useState('');
return (
<>
<ChildA value={sharedValue} onChange={setSharedValue} />
<ChildB value={sharedValue} />
</>
);
}
Context API
const AppContext = createContext();
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const value = {
user,
theme,
setUser,
setTheme
};
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
}
function useApp() {
const context = useContext(AppContext);
if (!context) throw new Error('useApp must be within AppProvider');
return context;
}
Redux Toolkit
// Store setup
import { configureStore, createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => { state.value += 1; },
decrement: state => { state.value -= 1; },
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});
// Component usage
import { useSelector, useDispatch } from 'react-redux';
function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<p>{count}</p>
<button onClick={() => dispatch(increment())}>+</button>
</div>
);
}
Zustand
import create from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 })
}));
function Counter() {
const { count, increment, decrement } = useStore();
return (
<div>
<p>{count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}
TanStack Query (React Query)
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function Users() {
const queryClient = useQueryClient();
// Fetch data
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(res => res.json())
});
// Mutation
const mutation = useMutation({
mutationFn: (newUser) => fetch('/api/users', {
method: 'POST',
body: JSON.stringify(newUser)
}),
onSuccess: () => {
queryClient.invalidateQueries(['users']);
}
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{data.map(user => <div key={user.id}>{user.name}</div>)}
<button onClick={() => mutation.mutate({ name: 'New User' })}>
Add User
</button>
</div>
);
}
Questions & Answers
A: Use Context for low-frequency updates (theme, auth). Use Redux for complex state with frequent updates, time-travel debugging, or when you need middleware.
A: Split contexts by update frequency, memoize context values, use useMemo for context value objects, or use state management libraries like Zustand.
A: Local state is specific to a component and its children. Global state is accessible throughout the app. Keep state as local as possible.
A: Use React Query for server state (fetching, caching, syncing). Use Redux for client state (UI state, user interactions). They can work together.
A: Use controlled components with useState, uncontrolled with useRef, or form libraries like Formik/React Hook Form for complex forms.