React hooks cheatsheet.
Rules
- Hooks only at top level — no loops, conditions, nested functions.
- Only in components or other hooks.
- Don’t rename React’s hooks.
useState
const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);
const [data, setData] = useState(() => heavyInit()); // lazy
Updater form:
setCount((c) => c + 1); // safe for concurrent / batched updates
useEffect
useEffect(() => {
// side effect
return () => {
// cleanup
};
}, [dep1, dep2]);
Skipping the array runs every render. Empty [] runs once.
useLayoutEffect
useLayoutEffect(() => {
// runs synchronously AFTER DOM mutations, BEFORE paint
// Use to measure DOM, avoid flicker
}, [deps]);
Only use when you need to read layout before paint.
useMemo
const result = useMemo(() => expensiveCompute(data), [data]);
Cache expensive values. Don’t sprinkle everywhere — only when measurable.
useCallback
const onClick = useCallback((id: number) => doIt(id), [doIt]);
Cache function identity. Useful when passing to memoized children or as a hook dep.
useRef
const ref = useRef<HTMLDivElement>(null);
<div ref={ref} />
// Mutable value (not state):
const idRef = useRef(0);
idRef.current++; // doesn't trigger re-render
useReducer
type State = { count: number };
type Action = { type: "inc" } | { type: "dec" };
function reducer(s: State, a: Action): State {
switch (a.type) {
case "inc": return { count: s.count + 1 };
case "dec": return { count: s.count - 1 };
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });
dispatch({ type: "inc" });
Better than useState when state transitions are complex.
useContext
const Theme = createContext("light");
function Comp() {
const theme = useContext(Theme);
return <p>{theme}</p>;
}
useId
const id = useId();
return (
<>
<label htmlFor={id}>Name</label>
<input id={id} />
</>
);
Stable, unique ID per render tree position. SSR-safe.
useTransition
const [isPending, startTransition] = useTransition();
startTransition(() => {
setHeavyState(newValue); // marked as non-urgent
});
return isPending ? <Spinner /> : <List />;
Lower-priority updates that yield to user input.
useDeferredValue
const deferred = useDeferredValue(input);
// Renders with deferred during heavy updates; eventually catches up
useSyncExternalStore
const value = useSyncExternalStore(
(cb) => store.subscribe(cb),
() => store.getSnapshot(),
() => store.getServerSnapshot?.(),
);
Subscribe to external (non-React) state safely with concurrent rendering.
useImperativeHandle
const Input = forwardRef<{ focus: () => void }, Props>((props, ref) => {
const inner = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => inner.current?.focus(),
}));
return <input ref={inner} />;
});
Use sparingly — usually a sign of fighting the framework.
useDebugValue (devtools only)
function useUser(id: number) {
const u = ...;
useDebugValue(u ? `user ${u.id}` : "loading");
return u;
}
Custom hooks
function useToggle(initial = false) {
const [on, setOn] = useState(initial);
const toggle = useCallback(() => setOn((v) => !v), []);
return [on, toggle] as const;
}
const [open, toggle] = useToggle();
Convention: name starts with use.
use (React 19+)
function Comp() {
const data = use(fetchPromise); // suspends until resolved
return <div>{data}</div>;
}
Can be called conditionally, unlike other hooks. Works with Promises and Contexts.
useOptimistic (React 19+)
const [optimisticItems, addOptimistic] = useOptimistic(items, (state, newItem) => [...state, newItem]);
async function submit(item: Item) {
addOptimistic(item);
await save(item);
}
Immediate UI update, rollback on failure.
useActionState (React 19+)
const [state, formAction, isPending] = useActionState(
async (prev, formData) => { ... },
initialState,
);
<form action={formAction}>...</form>
useFormStatus (React 19+)
function Submit() {
const { pending } = useFormStatus();
return <button disabled={pending}>Save</button>;
}
Read parent <form> action state.
Common mistakes
- Hook inside
if— breaks rule of hooks. - Stale closure — function captures old state. Use updater form or refs.
- Missing dep — react-hooks/exhaustive-deps catches it.
- Object/array deps not memoized — effect fires every render.
useEffectfor derived state — compute during render instead.
Read this next
If you want my custom hooks library, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .