When to use useState, useReducer, Context, or external state libraries. Decision tree based on scope, complexity, and sharing needs.
When to use useState, useReducer, Context, or external state libraries. Decision tree based on scope, complexity, and sharing needs.
BeforeMerge offers hundreds of code review rules, guides, and detection patterns to help your team ship better code.
Choosing the right state tool depends on scope, complexity, and how many components need the data.
Is it server data (API/DB)?
YES → Use a data-fetching library (TanStack Query, SWR)
NO ↓
Is it used by only one component?
YES → useState or useReducer
NO ↓
Is it shared between parent and child?
YES → Lift state up + props
NO ↓
Is it shared across a subtree (< 10 consumers)?
YES → Context + useReducer
NO ↓
Is it global, frequently updated, or complex?
YES → External library (Zustand, Jotai)Simple, local state for a single component.
const [count, setCount] = useState(0);
const [form, setForm] = useState({ name: "", email: "" });Use when: Single value or small object, one component, simple updates.
State with complex transitions or multiple sub-values.
type State = { items: Item[]; loading: boolean; error: string | null };
type Action =
| { type: "FETCH_START" }
| { type: "FETCH_SUCCESS"; items: Item[] }
| { type: "FETCH_ERROR"; error: string };
function reducer(state: State, action: Action): State {
switch (action.type) {
case "FETCH_START": return { ...state, loading: true, error: null };
case "FETCH_SUCCESS": return { items: action.items, loading: false, error: null };
case "FETCH_ERROR": return { ...state, loading: false, error: action.error };
}
}Use when: Multiple related state transitions, state machine patterns, testable state logic.
Share state across a component subtree without prop drilling.
const ThemeContext = createContext<"light" | "dark">("light");
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<"light" | "dark">("light");
return (
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
);
}Use when: Theme, locale, auth status — values that change infrequently and are needed by many components.
Avoid when: Frequently updating values (causes re-renders of all consumers).
import { create } from "zustand";
const useStore = create<{ count: number; inc: () => void }>((set) => ({
count: 0,
inc: () => set((s) => ({ count: s.count + 1 })),
}));import { atom, useAtom } from "jotai";
const countAtom = atom(0);
const [count, setCount] = useAtom(countAtom);Use when: Global state, frequent updates, complex derived state, or you need middleware (persist, devtools).
useState.useState. Use TanStack Query.