Cheat Sheet
Quick reference — all built-in hooks, JSX rules, effect deps, and a performance checklist.
Built-in hooks#
| Hook | One-liner |
|---|---|
useState |
Local reactive state; returns [value, setValue] |
useReducer |
State via reducer; for complex/related transitions |
useEffect |
Side effects after paint; cleanup + deps |
useLayoutEffect |
Like useEffect but fires before paint (measure/mutate DOM) |
useInsertionEffect |
Injects styles before layout; for CSS-in-JS libs |
useContext |
Read a context value; subscribes to provider |
useRef |
Mutable box that persists across renders; no re-render |
useMemo |
Memoize an expensive computed value |
useCallback |
Memoize a function identity |
useImperativeHandle |
Customize the ref exposed by forwardRef |
useId |
Stable unique id for a11y/SSR-safe attributes |
useTransition |
Mark updates non-urgent; [isPending, startTransition] |
useDeferredValue |
Defer a value to keep UI responsive |
useSyncExternalStore |
Subscribe to external stores safely (concurrent) |
useDebugValue |
Label custom hooks in DevTools |
use (19) |
Read a promise/context in render; suspends on a promise |
useOptimistic (19) |
Optimistic UI state during async actions |
useActionState (19) |
State bound to a form action [state, action, pending] |
useFormStatus (19) |
Pending/status of the enclosing <form> |
JSX rules#
- Return a single root; use a Fragment
<>...</>for siblings. - Attributes are camelCase:
className,htmlFor,onClick,tabIndex. - Expressions in
{};false/null/undefinedrender nothing (but0renders0). - Every list item needs a stable
key. - Close every tag, including self-closing:
<img />,<br />. styletakes an object:style={{ color: "red", fontSize: 12 }}.
Controlled input snippet#
const [value, setValue] = useState("");
<input value={value} onChange={(e) => setValue(e.target.value)} />;
// checkbox
<input type="checkbox" checked={on} onChange={(e) => setOn(e.target.checked)} />;
Effect deps rules#
| Deps array | Runs |
|---|---|
| omitted | After every render |
[] |
Once after mount (cleanup on unmount) |
[a, b] |
On mount + whenever a or b change |
🟢 Include every reactive value used inside the effect (props, state, functions). Missing deps → stale closures. Wrap helper functions in useCallback or define them inside the effect.
⚠️ Effects run twice in dev StrictMode (mount→unmount→mount) to surface missing cleanup — this is intentional.
Lifecycle mapping (class → hooks)#
| Class | Hook equivalent |
|---|---|
componentDidMount |
useEffect(fn, []) |
componentDidUpdate |
useEffect(fn, [deps]) |
componentWillUnmount |
cleanup return in useEffect |
shouldComponentUpdate |
React.memo |
Render flow#
flowchart LR
A["State/props change"] --> B["Render (build new tree)"]
B --> C["Reconcile / diff vs previous"]
C --> D["Commit DOM changes"]
D --> E["useLayoutEffect (sync)"]
E --> F["Browser paint"]
F --> G["useEffect (async)"]
Performance checklist#
- ✅ Stable
keys (ids, not index). - ✅ Derive during render; avoid effects for computed data.
- ✅ Functional updates
setX(prev => ...)to dodge stale closures. - ✅
React.memo+useCallback/useMemoonly where profiled. - ✅ Split large contexts; memoize provider
value. - ✅ Code-split with
React.lazy+<Suspense>. - ✅ Virtualize long lists (react-window/virtual).
- ✅ Use
useTransition/useDeferredValuefor expensive updates. - ✅ Keep client components at the leaves (RSC) to shrink JS.
Common patterns#
// Lazy + Suspense
const Chart = React.lazy(() => import("./Chart"));
<Suspense fallback={<Spinner />}><Chart /></Suspense>
// Ref to DOM node
const inputRef = useRef(null);
<input ref={inputRef} />; // inputRef.current.focus()
// Conditional render
{isLoading ? <Spinner /> : <List items={items} />}
{items.length > 0 && <List items={items} />}
// Custom hook
function usePrevious(value) {
const ref = useRef();
useEffect(() => { ref.current = value; }, [value]);
return ref.current;
}
Interview Q&A#
Q1. useEffect vs useLayoutEffect?
Both run after render; useEffect fires asynchronously after paint, useLayoutEffect fires synchronously before paint. Use useLayoutEffect to measure/mutate the DOM before the user sees it, else prefer useEffect.
Q2. Why does 0 show up in the UI but false doesn't?
React renders numbers, and 0 is a number; false/null/undefined render nothing. So {count && <X/>} prints 0 — guard with {count > 0 && <X/>}.
Q3. What belongs in the dependency array? Every reactive value referenced inside the effect — props, state, and functions/objects derived from them. Omitting them causes stale reads; the exhaustive-deps lint rule enforces this.
Q4. Name a few React 19 hooks.
use (read a promise/context in render, suspends), useOptimistic (optimistic UI), useActionState and useFormStatus (form action state/pending). React 19 also auto-memoizes via the Compiler.
Q5. When do you reach for useReducer over useState?
When state transitions are complex, interrelated, or the next state depends on the previous in non-trivial ways — a reducer centralizes the logic and makes updates testable and predictable.