pencils ready ✎

React MCQs multiple-choice questions with answers & explanations

All 26 React quiz questions on one page. Pick an answer in your head, then open Show answer to check it and read why. Want a score and a timer? Take them as a quiz instead.

  1. 1.

    You type something into each of the three uncontrolled inputs, then click "Remove first". What do you see?

    mid
    function Todos() {
      const [items, setItems] = useState(['a', 'b', 'c']);
      return (
        <>
          <button onClick={() => setItems(items.slice(1))}>Remove first</button>
          {items.map((item, i) => (
            <label key={i}>
              {item} <input />
            </label>
          ))}
        </>
      );
    }
    1. ARows b and c remain, each still showing the text you typed next to them
    2. BAll remaining inputs are cleared because the list re-rendered
    3. CLabels b and c remain, but their inputs show the text typed for a and b; the text typed for c is gone
    4. DReact warns about duplicate keys and keeps all three rows
    Show answer

    Answer: C (Labels b and c remain, but their inputs show the text typed for a and b; the text typed for c is gone)

    With key={i}, keys 0 and 1 still exist after the deletion, so React reuses those two DOM rows (including the uncontrolled input values) and removes the row with key 2. The label text updates from props, but the typed values stay where they were, so they appear attached to the wrong items. Use a stable id from the data as the key.

  2. 2.

    What does the button show after one click?

    easy
    function Counter() {
      const [count, setCount] = useState(0);
      function handleClick() {
        setCount(count + 1);
        setCount(count + 1);
        setCount(count + 1);
      }
      return <button onClick={handleClick}>{count}</button>;
    }
    1. A3
    2. B1
    3. C0
    4. DIt flashes 1, 2, 3 and settles on 3
    Show answer

    Answer: B (1)

    count is a snapshot: it is 0 for the whole event handler, so all three calls queue "set to 1". React batches them into a single re-render. To increment three times, use the updater form setCount(c => c + 1).

  3. 3.

    Starting from n = 0, what is n after one click?

    hard
    const [n, setN] = useState(0);
    
    function handleClick() {
      setN((x) => x + 1);
      setN(n + 5);
      setN((x) => x * 2);
    }
    1. A2
    2. B12
    3. C5
    4. D10
    Show answer

    Answer: D (10)

    React processes the queue in order: x => x + 1 gives 1, then setN(n + 5) replaces it with 0 + 5 (the snapshot value of n is 0), then x => x * 2 doubles 5 to 10. A plain value replaces whatever is queued before it; an updater function receives the result of the previous step.

  4. 4.

    name starts as 'Ada'. What does the click handler log?

    easy
    const [name, setName] = useState('Ada');
    
    function handleClick() {
      setName('Grace');
      console.log(name);
    }
    1. A'Ada'
    2. B'Grace'
    3. Cundefined
    4. DIt depends on whether React has already re-rendered
    Show answer

    Answer: A ('Ada')

    Setting state doesn't change the variable in the current render; it schedules a new render in which name will be 'Grace'. The running handler closed over the old snapshot, so it logs 'Ada' every time.

  5. 5.

    The component is rendered inside <StrictMode> in development. What is logged on mount?

    mid
    function ChatRoom() {
      useEffect(() => {
        console.log('connect');
        return () => console.log('disconnect');
      }, []);
      return <h1>Chat</h1>;
    }
    1. Aconnect
    2. Bconnect, connect
    3. Cconnect, disconnect, connect
    4. Ddisconnect, connect
    Show answer

    Answer: C (connect, disconnect, connect)

    In development, StrictMode runs one extra setup + cleanup cycle on mount to verify that your cleanup mirrors your setup. In production you only see connect. The fix for problems this exposes is a correct cleanup, not removing StrictMode.

  6. 6.

    In what order are the effects logged when Parent mounts?

    hard
    function Child() {
      useEffect(() => console.log('child effect'));
      useLayoutEffect(() => console.log('child layout'));
      return null;
    }
    
    function Parent() {
      useEffect(() => console.log('parent effect'));
      useLayoutEffect(() => console.log('parent layout'));
      return <Child />;
    }
    1. Aparent layout, child layout, parent effect, child effect
    2. Bchild layout, parent layout, child effect, parent effect
    3. Cchild layout, child effect, parent layout, parent effect
    4. Dparent effect, parent layout, child effect, child layout
    Show answer

    Answer: B (child layout, parent layout, child effect, parent effect)

    All layout effects run synchronously after the DOM is updated (before paint), then passive effects run later. Within each group, children run before parents, so a parent effect can rely on its children already being mounted.

  7. 7.

    A tooltip must measure its own height and reposition itself above its anchor without the user ever seeing it in the wrong spot. Where should you measure?

    mid
    1. AIn useEffect
    2. BDuring render, via ref.current.getBoundingClientRect()
    3. CIn useMemo with the anchor as a dependency
    4. DIn useLayoutEffect
    Show answer

    Answer: D (In useLayoutEffect)

    useLayoutEffect runs after the DOM is updated but before the browser paints, so you can measure and set state and the user only sees the final position. useEffect usually runs after paint, causing a visible flicker, and reading refs during render is unreliable because the DOM may not be updated yet.

  8. 8.

    Does Chart re-render when the button is clicked?

    mid
    const Chart = memo(function Chart({ options }: { options: { color: string } }) {
      console.log('Chart render');
      return <canvas />;
    });
    
    function Dashboard() {
      const [tick, setTick] = useState(0);
      return (
        <>
          <button onClick={() => setTick(tick + 1)}>{tick}</button>
          <Chart options={{ color: 'red' }} />
        </>
      );
    }
    1. ANo: memo skips it because the props are deeply equal to last time
    2. BNo: Chart doesn't read tick, so memo knows it's unaffected
    3. CYes: the inline object is a new reference each render, so memo's shallow check fails
    4. DYes: memo only works for class components, so it has no effect here
    Show answer

    Answer: C (Yes: the inline object is a new reference each render, so memo's shallow check fails)

    memo does a shallow comparison of props. An inline object literal is a new reference on every render of Dashboard, so the comparison fails and Chart re-renders. Hoist the constant out of the component or wrap it in useMemo.

  9. 9.

    handleClick is wrapped in useCallback and passed to <Button onClick={handleClick} />. Button is a plain function component (not wrapped in memo). What does useCallback buy you here?

    mid
    1. AEssentially nothing: Button re-renders whenever its parent does anyway
    2. BButton skips re-rendering, because its onClick prop is now stable
    3. CThe handler is created only once, so each render does much less work
    4. DIt guarantees the handler always sees the latest state values
    Show answer

    Answer: A (Essentially nothing: Button re-renders whenever its parent does anyway)

    A stable function identity only matters to something that compares it: a memo child, or a dependency array of an effect/useMemo. A non-memoized child re-renders with its parent anyway. And useCallback can actually cause stale closures if dependencies are missing; it doesn't prevent them.

  10. 10.

    App renders <ThemeContext value={{ theme }}> and re-renders because of an unrelated state change; theme is unchanged. What happens to a memo-wrapped component that calls useContext(ThemeContext)?

    hard
    1. AIt's skipped: memo prevents re-renders unless its own props change
    2. BIt's skipped: React compares context values deeply, and theme is unchanged
    3. CIt re-renders: the { theme } object is new each time, so the context value changed
    4. DOnly its children re-render; the memoized component itself is skipped
    Show answer

    Answer: C (It re-renders: the { theme } object is new each time, so the context value changed)

    Context consumers re-render whenever the provider value changes by Object.is, and memo does not stop a context-triggered render. The inline { theme } object is new every time App renders. Memoize it: const value = useMemo(() => ({ theme }), [theme]).

  11. 11.

    You click the button three times. What number does it display?

    easy
    function Clicks() {
      const clicks = useRef(0);
      return (
        <button onClick={() => { clicks.current++; }}>
          {clicks.current}
        </button>
      );
    }
    1. A3
    2. B1
    3. C0
    4. DNaN
    Show answer

    Answer: C (0)

    Mutating ref.current does not trigger a re-render, so the button keeps showing the value from the first render. The ref does hold 3 internally. Anything shown on screen should live in state.

  12. 12.

    A component renders <input value={name} /> with no onChange. What happens when the user types?

    easy
    1. AThe input updates normally and name is updated automatically
    2. BThe text doesn't change, and React warns that you provided value without an onChange handler
    3. CReact throws and unmounts the component
    4. DThe input silently becomes uncontrolled after the first keystroke
    Show answer

    Answer: B (The text doesn't change, and React warns that you provided value without an onChange handler)

    Passing value makes the input controlled: React forces the DOM value back to name on every render, so keystrokes appear to do nothing. Add onChange to update state, use defaultValue for an uncontrolled input, or readOnly if it's intentional.

  13. 13.

    Which of these errors will an error boundary catch?

    mid
    1. AAn error thrown inside a button's onClick handler
    2. BAn error thrown in a setTimeout callback
    3. CA rejected promise inside an async function called from useEffect
    4. DAn error thrown while rendering a child component
    Show answer

    Answer: D (An error thrown while rendering a child component)

    Error boundaries catch errors thrown during rendering (and in lifecycle methods) of the tree below them. Errors in event handlers and asynchronous code happen outside rendering, so you handle them with try/catch, or set state that makes a component throw during its next render.

  14. 14.

    What's wrong with this code?

    mid
    function Page() {
      const Editor = lazy(() => import('./Editor'));
      return (
        <Suspense fallback={<Spinner />}>
          <Editor />
        </Suspense>
      );
    }
    1. AEach render calls lazy again, creating a new component type, so Editor remounts and loses state
    2. BNothing: calling lazy inside the component keeps the import scoped to this page
    3. CSuspense must be placed inside Editor, not around it
    4. Dlazy only works with named exports, so the import needs .then((m) => m.Editor)
    Show answer

    Answer: A (Each render calls lazy again, creating a new component type, so Editor remounts and loses state)

    Every call to lazy returns a new component type. Calling it during render means React sees a different type each time Page renders, so it tears down and remounts Editor, resetting its state. Declare lazy components at module level; note lazy expects the module's default export.

  15. 15.

    A modal is rendered with createPortal(<button>OK</button>, document.body) from inside a <div onClick={log}>. When the button is clicked, does the div's onClick fire?

    mid
    1. ANo: the button isn't a DOM descendant of the div
    2. BOnly if the portal target is inside the div
    3. CYes: React events propagate through the React component tree, not the DOM tree
    4. DOnly for capture-phase handlers (onClickCapture)
    Show answer

    Answer: C (Yes: React events propagate through the React component tree, not the DOM tree)

    A portal changes where the DOM nodes go, but the component still lives in the same place in the React tree: it receives context from its React parents and its events bubble to React ancestors. Call e.stopPropagation() in the portal if that is unwanted.

  16. 16.

    Which of these breaks the Rules of Hooks?

    easy
    1. ACalling useState twice in a row at the top of a component
    2. BCalling useEffect inside an if (user) block
    3. CCalling use(promise) inside an if block
    4. DCalling useMemo inside a custom hook
    Show answer

    Answer: B (Calling useEffect inside an if (user) block)

    Hooks like useEffect must be called unconditionally at the top level, because React identifies them by call order between renders. Put the condition inside the effect instead. use is the documented exception: it can be called conditionally and in loops.

  17. 17.

    Sidebar and Header both call a custom hook useToggle() that wraps useState(false). Toggling in Sidebar does what to Header?

    easy
    1. ANothing: each call to a custom hook has its own independent state
    2. BHeader toggles too, because they share the same hook
    3. CHeader re-renders but keeps its value
    4. DIt throws, because a custom hook can only be used by one component
    Show answer

    Answer: A (Nothing: each call to a custom hook has its own independent state)

    Custom hooks share stateful logic, not state. Each component calling useToggle() gets its own useState slot. To share one value, lift the state up or put it in context or a store.

  18. 18.

    You wrap setQuery(e.target.value) for a controlled search <input> in startTransition to keep typing fast. What is the problem?

    hard
    1. ATransitions can't be started from event handlers, so the call is ignored
    2. BstartTransition only accepts async functions, so the update throws
    3. CTransition updates are interruptible, so the input's own value can lag behind typing
    4. DEach keystroke starts a transition that re-triggers itself: an infinite render loop
    Show answer

    Answer: C (Transition updates are interruptible, so the input's own value can lag behind typing)

    A text input needs its value updated synchronously. The recommended pattern is two pieces of state (or useDeferredValue): update the input's state normally and let the slow list render from a transition or a deferred copy of the query.

  19. 19.

    The user types "a" into an empty field. On the first re-render after the keystroke, what are the two values?

    mid
    const [query, setQuery] = useState('');
    const deferredQuery = useDeferredValue(query);
    
    return (
      <>
        <input value={query} onChange={(e) => setQuery(e.target.value)} />
        <SlowList query={deferredQuery} />   {/* SlowList is wrapped in memo */}
      </>
    );
    1. ABoth are 'a', because they update in the same render
    2. Bquery is 'a' and deferredQuery is still ''; a background render then catches up
    3. Cquery is still '' and deferredQuery is 'a', because the input is deferred
    4. DdeferredQuery only updates after a fixed ~300 ms debounce has passed
    Show answer

    Answer: B (query is 'a' and deferredQuery is still ''; a background render then catches up)

    React first re-renders urgently with the new query and the old deferred value, so the input updates immediately and the memoized SlowList is skipped. Then it renders again in the background with the new value, and that render can be interrupted by further typing. There's no fixed delay as with debouncing.

  20. 20.

    Which of these can you do directly in a React Server Component?

    mid
    1. AKeep a counter in useState and increment it on click
    2. BAttach an onClick handler to a button it renders
    3. CFetch data in useEffect after the component mounts
    4. DMake the component async and await a database query in its body
    Show answer

    Answer: D (Make the component async and await a database query in its body)

    Server Components run only on the server, so they can be async and access data sources directly, but they can't use state, effects, or event handlers. Interactive parts go into a Client Component marked with 'use client'.

  21. 21.

    Why doesn't the button ever become disabled while save is running?

    hard
    import { useFormStatus } from 'react-dom';
    
    function EditForm() {
      const { pending } = useFormStatus();
      return (
        <form action={save}>
          <input name="title" />
          <button disabled={pending}>Save</button>
        </form>
      );
    }
    1. Aaction must be a URL string; with a function, no pending state is tracked
    2. Bpending is only true for forms submitted with method="get"
    3. CThe button needs an explicit type="submit" for the status to update
    4. DuseFormStatus only reads a parent form, so it must be called inside the <form>
    Show answer

    Answer: D (useFormStatus only reads a parent form, so it must be called inside the <form>)

    useFormStatus reads the status of the form that contains the component calling it, so called in the component that renders the <form>, it never sees that form's submission. Move the button into its own SubmitButton component that calls the hook. Buttons inside a form already default to type="submit".

  22. 22.

    A parent's state changes. Its child <Footer /> receives no props and isn't wrapped in memo. Does Footer re-render?

    easy
    1. ANo: React only re-renders children whose props changed
    2. BOnly if Footer reads a context value that changed
    3. CYes: when a component renders, React renders its children too by default
    4. DOnly in development, because of StrictMode double rendering
    Show answer

    Answer: C (Yes: when a component renders, React renders its children too by default)

    By default, rendering a component re-renders its whole subtree; React doesn't check props unless the child is wrapped in memo. This is usually cheap. If it isn't, memoize the child, move the state down, or pass the child in as children from a component that doesn't re-render.

  23. 23.

    What happens when this component mounts?

    mid
    function Results({ query }: { query: string }) {
      const [data, setData] = useState<Item[]>([]);
      const options = { query, limit: 10 };
    
      useEffect(() => {
        fetchResults(options).then(setData);
      }, [options]);
    
      return <List items={data} />;
    }
    1. AIt fetches once on mount and again only when query changes
    2. BIt fetches over and over: options is a new object each render, and every setData causes another render
    3. CReact detects the object dependency and skips the effect
    4. DIt fetches once, because effects with dependencies never re-run on their own
    Show answer

    Answer: B (It fetches over and over: options is a new object each render, and every setData causes another render)

    Dependencies are compared with Object.is, and options is recreated on every render, so the effect runs after every render. Each fetch stores a new array in state, which re-renders, which re-runs the effect. Create the object inside the effect and depend on query instead.

  24. 24.

    What does the button show after two clicks?

    easy
    function Counter() {
      const [state, setState] = useState({ count: 0 });
      function handleClick() {
        state.count++;
        setState(state);
      }
      return <button onClick={handleClick}>{state.count}</button>;
    }
    1. A2
    2. B1
    3. C0
    4. DIt throws because state is frozen
    Show answer

    Answer: C (0)

    setState(state) passes the same object reference, so Object.is says nothing changed and React bails out without re-rendering; the screen keeps showing 0 even though the object was mutated. Treat state as immutable: setState({ ...state, count: state.count + 1 }).

  25. 25.

    A Profile component keeps a local draft in useState. The parent switches from user 1 to user 2 by rendering <Profile key={userId} userId={userId} />. What happens?

    mid
    1. AThe same instance receives the new userId; the draft for user 1 stays
    2. BReact unmounts the old Profile and mounts a fresh one, so all its state (and effects) start over for user 2
    3. Ckey is ignored outside of lists
    4. DReact warns that key cannot be read as a prop
    Show answer

    Answer: B (React unmounts the old Profile and mounts a fresh one, so all its state (and effects) start over for user 2)

    A different key tells React it is a different component instance, so the old one is unmounted (cleanups run) and a new one mounts with fresh state. This is the recommended way to reset state when an identity prop changes, instead of an effect that clears state. Without the key, the draft for user 1 would carry over.

  26. 26.

    What is useId intended for?

    easy
    1. AGenerating stable keys for the items of a rendered list
    2. BCreating unique ids for new records before they're saved to the database
    3. CGenerating ids for accessibility attributes that match between server and client
    4. DReading the id attribute of the component's root DOM element
    Show answer

    Answer: C (Generating ids for accessibility attributes that match between server and client)

    useId returns a stable, unique string per component instance, for attributes like htmlFor or aria-describedby. It is identical in server-rendered HTML and on the client, avoiding the hydration mismatches that Math.random() would cause. It shouldn't be used for list keys; keys should come from your data.

esc