State management approaches in React
State Management in React
Choosing the right state management approach depends on what kind of state you're managing and how widely it's shared. React offers built-in options, and the ecosystem provides powerful external libraries.
Types of State
| Type | Examples | Best approach |
|---|---|---|
| Local/UI state | Form inputs, toggles, modals | useState, useReducer |
| Shared state | Theme, auth, cart | Context, Zustand, Redux |
| Server state | API data, cache | TanStack Query, SWR |
| URL state | Filters, pagination | URL params, search params |
| Form state | Complex form validation | React Hook Form, Formik |
Built-in: useState
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}Best for: Simple, local component state.
Built-in: useReducer
type Action = { type: "increment" } | { type: "decrement" } | { type: "reset" };
function reducer(state: number, action: Action): number {
switch (action.type) {
case "increment": return state + 1;
case "decrement": return state - 1;
case "reset": return 0;
}
}
function Counter() {
const [count, dispatch] = useReducer(reducer, 0);
return (
<div>
<span>{count}</span>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</div>
);
}Best for: Complex local state with multiple actions.
Built-in: Context API
const ThemeContext = createContext<"light" | "dark">("light");
function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<"light" | "dark">("light");
return (
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>Click me</button>;
}Best for: Infrequently changing global values (theme, locale, auth).
⚠️ Limitation: all consumers re-render when the context value changes.
Zustand (Lightweight Store)
import { create } from "zustand";
interface CartStore {
items: Product[];
addItem: (item: Product) => void;
removeItem: (id: string) => void;
total: () => number;
}
const useCartStore = create<CartStore>((set, get) => ({
items: [],
addItem: (item) => set(state => ({ items: [...state.items, item] })),
removeItem: (id) => set(state => ({
items: state.items.filter(i => i.id !== id)
})),
total: () => get().items.reduce((sum, i) => sum + i.price, 0),
}));
// Usage — only subscribes to what it reads
function CartCount() {
const count = useCartStore(state => state.items.length);
return <span>{count}</span>;
}Best for: Simple to medium shared state. Minimal boilerplate, great performance.
Redux Toolkit
import { createSlice, configureStore } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1 },
decrement: (state) => { state.value -= 1 },
},
});
const store = configureStore({ reducer: { counter: counterSlice.reducer } });Best for: Large apps with complex state, middleware, devtools.
TanStack Query (Server State)
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
function Users() {
const { data, isLoading, error } = useQuery({
queryKey: ["users"],
queryFn: () => fetch("/api/users").then(r => r.json()),
});
if (isLoading) return <Spinner />;
return <UserList users={data} />;
}Best for: Server state — caching, background refetching, optimistic updates, pagination.
Decision Guide
Is it local to ONE component?
→ useState / useReducer
Is it shared by a FEW nearby components?
→ Lifting state up
Is it global but changes RARELY (theme, auth)?
→ Context API
Is it global and changes OFTEN?
→ Zustand (simple) or Redux Toolkit (complex)
Is it data from an API?
→ TanStack Query / SWRImportant:
There's no single best solution — use the right tool for each type of state. Start with useState, lift state up when needed, add Context for globals, use TanStack Query for server data, and reach for Zustand or Redux only when truly needed. Avoid putting everything in global state.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.