DavidGadyan

React: Hooks Cheatsheet and Practice Quiz

Twelve React patterns behind most assessment questions: components, useState, immutable updates, effects, forms, memoisation and the Next.js App Router, with a 20-question practice quiz.

Syntax cheatsheet

12 patterns. Pick one on the map for its syntax, when to use it and the trap behind it.

  1. Basics

    Components and props

    Functions that return UI

    Syntax
    function User({ name, age = 0, children }) {
      return <h1>Hello {name} ({age}){children}</h1>;
    }
    <User name="David" age={33}>!</User>
    When to use it
    Every piece of UI. Props flow from parent to child; a child reads its props and never edits them.
    What it does
    A component is a function of props that returns JSX. React calls it again whenever its props or state change. children is whatever sits between the opening and closing tags.
    Watch out
    Props are read-only. Component names must start with a capital letter or JSX treats them as HTML tags. Return one root element or a fragment <>…</>.
    Example
    function Card({ title, children }) { return ( <section className="card"> <h2>{title}</h2> {children} </section> ); }
  2. Basics

    Lists, keys and conditionals

    Render collections

    Syntax
    {users.map(u => <User key={u.id} user={u} />)}
    {loading ? <Spinner /> : <List />}
    {error && <p role="alert">{error}</p>}
    When to use it
    Any array to display, and any UI that depends on state: loading, error, empty.
    What it does
    Keys let React match list items between renders so DOM nodes and state stay with the right item. Conditionals are plain JavaScript expressions inside braces.
    Watch out
    key={index} breaks when the list is reordered, filtered or has insertions: use a stable id. count && <X /> renders 0 when count is 0: write count > 0 &&.
    Example
    {items.length === 0 ? <Empty /> : <ul>{items.map(it => <li key={it.id}>{it.name}</li>)}</ul>}
  3. State

    useState and functional updates

    Local state

    Syntax
    const [count, setCount] = useState(0);
    setCount(count + 1);          // uses this render's value
    setCount(prev => prev + 1);   // uses the latest value
    When to use it
    Any value that changes over time and should re-render the component: inputs, toggles, fetched data, counters.
    What it does
    useState returns the current value and a setter; calling the setter schedules a re-render with the new value. Several updates inside one event are batched into one render.
    Watch out
    Two setCount(count + 1) calls in one handler add 1, not 2, because count is a snapshot of this render: use the functional form. State belongs to the component instance, it is not shared between instances.
    Example
    function handleClick() { setCount(prev => prev + 1); setCount(prev => prev + 1); // +2 } const [form, setForm] = useState({ name: '', email: '' }); const [items, setItems] = useState(() => expensiveInitial()); // lazy initial state
  4. State

    Immutable state updates

    Never mutate

    Syntax
    setUser(prev => ({ ...prev, name: 'John' }));                              // change a field
    setUsers(prev => [...prev, newUser]);                                        // add
    setUsers(prev => prev.filter(u => u.id !== id));                             // remove
    setUsers(prev => prev.map(u => u.id === id ? { ...u, done: true } : u));    // edit one
    When to use it
    Every object or array held in state.
    What it does
    React compares references to decide what changed; a new object or array signals an update, a mutated one looks identical and may be skipped.
    Watch out
    user.name = "x"; setUser(user) re-uses the same reference and may not re-render. push, splice, sort and reverse mutate; spread, concat, filter, map and toSorted do not.
    Example
    // wrong: same reference users.push(newUser); setUsers(users); // right: new array setUsers(prev => [...prev, newUser]); // nested field setState(prev => ({ ...prev, address: { ...prev.address, city } }));
  5. Effects

    useEffect and cleanup

    Sync with the outside

    Syntax
    useEffect(() => {
      const id = setInterval(tick, 1000);
      return () => clearInterval(id);   // cleanup
    }, []);                              // deps: once after mount
    useEffect(() => { fetchUser(userId); }, [userId]);   // when userId changes
    When to use it
    Subscriptions, timers, manual DOM APIs, fetching when an id changes. Not for deriving values from props or state: compute those during render.
    What it does
    The effect runs after the commit: with [] only after the first render, with [a, b] whenever a or b change, with no array after every render. The returned function runs before the next run and on unmount.
    Watch out
    A missing dependency reads stale values. Setting state unconditionally inside an effect that depends on that state loops forever. In development Strict Mode runs effects twice to test the cleanup.
    Example
    useEffect(() => { const ctrl = new AbortController(); fetch(`/api/users/${id}`, { signal: ctrl.signal }) .then(r => r.json()) .then(setUser) .catch(() => {}); return () => ctrl.abort(); }, [id]);
  6. Effects

    Fetch with loading and error

    Three states

    Syntax
    const [users, setUsers] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    useEffect(() => {
      let alive = true;
      fetch('/api/users')
        .then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); })
        .then(data => { if (alive) setUsers(data); })
        .catch(e => { if (alive) setError(e.message); })
        .finally(() => { if (alive) setLoading(false); });
      return () => { alive = false; };
    }, []);
    When to use it
    A component that loads its own data. In Next.js prefer fetching in a Server Component and passing the data down as props.
    What it does
    Loading, error and data are three explicit states and the component renders one of them. The alive flag ignores results that arrive after unmount or after the id has changed.
    Watch out
    fetch does not throw on 404 or 500: check r.ok. The effect callback itself cannot be async; define an async function inside it and call it.
    Example
    if (loading) return <p>Loading…</p>; if (error) return <p role="alert">{error}</p>; return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
  7. Forms

    Controlled inputs and forms

    State drives the input

    Syntax
    const [name, setName] = useState('');
    <form onSubmit={e => { e.preventDefault(); save(name); }}>
      <input value={name} onChange={e => setName(e.target.value)} />
      <button type="submit">Save</button>
    </form>
    When to use it
    Validation while typing, dependent fields, resetting a form, anything where React must know the value at all times.
    What it does
    A controlled input shows the state value and reports every change, so state is the single source of truth. An uncontrolled input keeps its own value, read through a ref when needed.
    Watch out
    value without onChange makes the input read-only and React warns. Forgetting preventDefault reloads the page on submit. Checkboxes bind with checked, not value.
    Example
    const [form, setForm] = useState({ email: '', agree: false }); const onChange = e => { const { name, type, value, checked } = e.target; setForm(prev => ({ ...prev, [name]: type === 'checkbox' ? checked : value })); }; <input name="email" value={form.email} onChange={onChange} /> <input name="agree" type="checkbox" checked={form.agree} onChange={onChange} />
  8. State

    Props vs state, lifting state up

    Where data lives

    Syntax
    function Parent() {
      const [query, setQuery] = useState('');
      return (
        <>
          <SearchBox value={query} onChange={setQuery} />
          <Results query={query} />
        </>
      );
    }
    When to use it
    Two siblings need the same value: move it to their closest common parent and pass it down with a setter callback.
    What it does
    Props are external input the parent owns; state is internal data the component owns. Lifting keeps one source of truth so the siblings can never disagree.
    Watch out
    Copying a prop into state (useState(props.value)) freezes it at mount. Do not store what you can compute: derive fullName from first and last during render.
    Example
    function SearchBox({ value, onChange }) { return <input value={value} onChange={e => onChange(e.target.value)} />; } const fullName = `${first} ${last}`; // derived, not state
  9. Performance

    useMemo, useCallback, React.memo

    Skip repeated work

    Syntax
    const sorted = useMemo(() => [...items].sort(byName), [items]);
    const onSelect = useCallback(id => setSelected(id), []);
    const Row = React.memo(function Row({ item, onSelect }) { /* … */ });
    When to use it
    A calculation is measurably expensive, or a memoised child re-renders because it receives a new function or object on every render.
    What it does
    useMemo caches a value and useCallback a function until the dependencies change. React.memo skips re-rendering a component whose props are shallow-equal to last time.
    Watch out
    Memoising everything costs more than it saves: measure first. An inline object or arrow function prop defeats React.memo. The React Compiler does most of this automatically in new projects.
    Example
    // new object every render: Row re-renders anyway <Row style={{ color: 'red' }} /> // stable reference: hoist it or memoise it const style = useMemo(() => ({ color: 'red' }), []); <Row style={style} />
  10. More hooks

    useRef

    A box that survives renders

    Syntax
    const inputRef = useRef(null);
    <input ref={inputRef} />
    inputRef.current.focus();           // in a handler or effect
    
    const timer = useRef(null);         // mutable value, no re-render
    timer.current = setTimeout(fn, 500);
    When to use it
    Focusing or measuring DOM nodes, holding timers, previous values or any mutable that must not trigger a render.
    What it does
    useRef returns the same { current } object on every render; changing current does not re-render, unlike state.
    Watch out
    Do not read or write ref.current during render (the React Compiler flags it): use it in handlers and effects. A ref is per instance, like state.
    Example
    useEffect(() => { inputRef.current?.focus(); }, []); const prev = useRef(value); useEffect(() => { prev.current = value; }, [value]); // previous value
  11. More hooks

    useContext and useReducer

    Shared and complex state

    Syntax
    const ThemeContext = createContext('light');
    <ThemeContext value="dark"><App /></ThemeContext>
    const theme = useContext(ThemeContext);
    
    const [state, dispatch] = useReducer(reducer, initial);
    dispatch({ type: 'add', item });
    When to use it
    Context for values many components need (theme, user, locale) without prop drilling. useReducer when several fields change together or an update depends on an action type.
    What it does
    Every consumer re-renders when the provided value changes. A reducer is a pure (state, action) => newState function, so updates are predictable and easy to test.
    Watch out
    A provider value created inline ({ user, setUser }) changes every render and re-renders all consumers: memoise it. Context is not a store; for frequent updates split contexts or use a state library.
    Example
    function reducer(state, action) { switch (action.type) { case 'add': return [...state, action.item]; case 'remove': return state.filter(i => i.id !== action.id); default: return state; } }
  12. Next.js

    Next.js App Router essentials

    Server first

    Syntax
    app/page.tsx                    // route: /
    app/users/[id]/page.tsx         // dynamic: /users/42
    app/layout.tsx                  // shared shell
    'use client';                   // first line: Client Component
    export function generateStaticParams() { return ids.map(id => ({ id })); }
    import { redirect } from 'next/navigation';
    When to use it
    Server Components by default (data access, zero client bundle); "use client" only for state, effects, browser APIs and event handlers.
    What it does
    Folders under app/ define routes, [id] a dynamic segment, layout.tsx shared UI. generateStaticParams lists the dynamic pages to prerender; middleware runs request-time logic such as auth, rewrites and redirects.
    Watch out
    Secrets go in server-only env vars, never NEXT_PUBLIC_*. A Client Component cannot import a Server Component, but it can receive one as children. Use next/image for optimised images.
    Example
    // app/users/[id]/page.tsx (Server Component) export default async function Page({ params }) { const { id } = await params; const user = await db.user.find(id); // server only, never shipped return <Profile user={user} />; }

Pick a pattern on the map to see its syntax, when to use it, what it does and what to watch out for.

Practice quiz

20 multiple-choice questions drawn at random from the 38 in this track, one per screen, graded at the end. Every question points back to a pattern on the map.

Pick an answer, move freely between the questions and finish whenever you like: the score and the explanation of every question appear at the end.

  • 20 questions
  • No time limit
  • Graded at the end

The quiz needs JavaScript. The cheatsheet above works without it.

Practice material for timed coding assessments. Behaviour described follows the React 19 and Next.js 16 App Router documentation.