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.mid
You type something into each of the three uncontrolled inputs, then click "Remove first". What do you see?
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> ))} </> ); }- ARows b and c remain, each still showing the text you typed next to them
- BAll remaining inputs are cleared because the list re-rendered
- CLabels b and c remain, but their inputs show the text typed for a and b; the text typed for c is gone
- 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}, keys0and1still exist after the deletion, so React reuses those two DOM rows (including the uncontrolled input values) and removes the row with key2. 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.easy
What does the button show after one click?
function Counter() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); setCount(count + 1); setCount(count + 1); } return <button onClick={handleClick}>{count}</button>; }- A3
- B1
- C0
- DIt flashes 1, 2, 3 and settles on 3
Show answer
Answer: B (1)
countis a snapshot: it is0for 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 formsetCount(c => c + 1). - 3.hard
Starting from
n = 0, what isnafter one click?const [n, setN] = useState(0); function handleClick() { setN((x) => x + 1); setN(n + 5); setN((x) => x * 2); }- A2
- B12
- C5
- D10
Show answer
Answer: D (10)
React processes the queue in order:
x => x + 1gives 1, thensetN(n + 5)replaces it with0 + 5(the snapshot value ofnis 0), thenx => x * 2doubles 5 to 10. A plain value replaces whatever is queued before it; an updater function receives the result of the previous step. - 4.easy
namestarts as'Ada'. What does the click handler log?const [name, setName] = useState('Ada'); function handleClick() { setName('Grace'); console.log(name); }- A'Ada'
- B'Grace'
- Cundefined
- 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
namewill be'Grace'. The running handler closed over the old snapshot, so it logs'Ada'every time. - 5.mid
The component is rendered inside
<StrictMode>in development. What is logged on mount?function ChatRoom() { useEffect(() => { console.log('connect'); return () => console.log('disconnect'); }, []); return <h1>Chat</h1>; }- Aconnect
- Bconnect, connect
- Cconnect, disconnect, connect
- 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.hard
In what order are the effects logged when
Parentmounts?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 />; }- Aparent layout, child layout, parent effect, child effect
- Bchild layout, parent layout, child effect, parent effect
- Cchild layout, child effect, parent layout, parent effect
- 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.mid
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?
- AIn
useEffect - BDuring render, via
ref.current.getBoundingClientRect() - CIn
useMemowith the anchor as a dependency - DIn
useLayoutEffect
Show answer
Answer: D (In
useLayoutEffect)useLayoutEffectruns 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.useEffectusually runs after paint, causing a visible flicker, and reading refs during render is unreliable because the DOM may not be updated yet. - AIn
- 8.mid
Does
Chartre-render when the button is clicked?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' }} /> </> ); }- ANo: memo skips it because the props are deeply equal to last time
- BNo: Chart doesn't read
tick, so memo knows it's unaffected - CYes: the inline object is a new reference each render, so memo's shallow check fails
- 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)
memodoes a shallow comparison of props. An inline object literal is a new reference on every render ofDashboard, so the comparison fails andChartre-renders. Hoist the constant out of the component or wrap it inuseMemo. - 9.mid
handleClickis wrapped inuseCallbackand passed to<Button onClick={handleClick} />.Buttonis a plain function component (not wrapped inmemo). What doesuseCallbackbuy you here?- AEssentially nothing: Button re-renders whenever its parent does anyway
- BButton skips re-rendering, because its
onClickprop is now stable - CThe handler is created only once, so each render does much less work
- 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
memochild, or a dependency array of an effect/useMemo. A non-memoized child re-renders with its parent anyway. AnduseCallbackcan actually cause stale closures if dependencies are missing; it doesn't prevent them. - 10.hard
Apprenders<ThemeContext value={{ theme }}>and re-renders because of an unrelated state change;themeis unchanged. What happens to amemo-wrapped component that callsuseContext(ThemeContext)?- AIt's skipped: memo prevents re-renders unless its own props change
- BIt's skipped: React compares context values deeply, and
themeis unchanged - CIt re-renders: the
{ theme }object is new each time, so the context value changed - 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, andmemodoes not stop a context-triggered render. The inline{ theme }object is new every timeApprenders. Memoize it:const value = useMemo(() => ({ theme }), [theme]). - 11.easy
You click the button three times. What number does it display?
function Clicks() { const clicks = useRef(0); return ( <button onClick={() => { clicks.current++; }}> {clicks.current} </button> ); }- A3
- B1
- C0
- DNaN
Show answer
Answer: C (0)
Mutating
ref.currentdoes 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.easy
A component renders
<input value={name} />with noonChange. What happens when the user types?- AThe input updates normally and
nameis updated automatically - BThe text doesn't change, and React warns that you provided
valuewithout anonChangehandler - CReact throws and unmounts the component
- DThe input silently becomes uncontrolled after the first keystroke
Show answer
Answer: B (The text doesn't change, and React warns that you provided
valuewithout anonChangehandler)Passing
valuemakes the input controlled: React forces the DOM value back tonameon every render, so keystrokes appear to do nothing. AddonChangeto update state, usedefaultValuefor an uncontrolled input, orreadOnlyif it's intentional. - AThe input updates normally and
- 13.mid
Which of these errors will an error boundary catch?
- AAn error thrown inside a button's
onClickhandler - BAn error thrown in a
setTimeoutcallback - CA rejected promise inside an async function called from
useEffect - 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.
- AAn error thrown inside a button's
- 14.mid
What's wrong with this code?
function Page() { const Editor = lazy(() => import('./Editor')); return ( <Suspense fallback={<Spinner />}> <Editor /> </Suspense> ); }- AEach render calls
lazyagain, creating a new component type, so Editor remounts and loses state - BNothing: calling
lazyinside the component keeps the import scoped to this page - C
Suspensemust be placed insideEditor, not around it - D
lazyonly works with named exports, so the import needs.then((m) => m.Editor)
Show answer
Answer: A (Each render calls
lazyagain, creating a new component type, so Editor remounts and loses state)Every call to
lazyreturns a new component type. Calling it during render means React sees a different type each timePagerenders, so it tears down and remountsEditor, resetting its state. Declare lazy components at module level; notelazyexpects the module's default export. - AEach render calls
- 15.mid
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'sonClickfire?- ANo: the button isn't a DOM descendant of the div
- BOnly if the portal target is inside the div
- CYes: React events propagate through the React component tree, not the DOM tree
- 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.easy
Which of these breaks the Rules of Hooks?
- ACalling
useStatetwice in a row at the top of a component - BCalling
useEffectinside anif (user)block - CCalling
use(promise)inside anifblock - DCalling
useMemoinside a custom hook
Show answer
Answer: B (Calling
useEffectinside anif (user)block)Hooks like
useEffectmust be called unconditionally at the top level, because React identifies them by call order between renders. Put the condition inside the effect instead.useis the documented exception: it can be called conditionally and in loops. - ACalling
- 17.easy
SidebarandHeaderboth call a custom hookuseToggle()that wrapsuseState(false). Toggling inSidebardoes what toHeader?- ANothing: each call to a custom hook has its own independent state
- BHeader toggles too, because they share the same hook
- CHeader re-renders but keeps its value
- 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 ownuseStateslot. To share one value, lift the state up or put it in context or a store. - 18.hard
You wrap
setQuery(e.target.value)for a controlled search<input>instartTransitionto keep typing fast. What is the problem?- ATransitions can't be started from event handlers, so the call is ignored
- B
startTransitiononly accepts async functions, so the update throws - CTransition updates are interruptible, so the input's own value can lag behind typing
- 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.mid
The user types "a" into an empty field. On the first re-render after the keystroke, what are the two values?
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 */} </> );- ABoth are 'a', because they update in the same render
- B
queryis 'a' anddeferredQueryis still ''; a background render then catches up - C
queryis still '' anddeferredQueryis 'a', because the input is deferred - D
deferredQueryonly updates after a fixed ~300 ms debounce has passed
Show answer
Answer: B (
queryis 'a' anddeferredQueryis still ''; a background render then catches up)React first re-renders urgently with the new
queryand the old deferred value, so the input updates immediately and the memoizedSlowListis 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.mid
Which of these can you do directly in a React Server Component?
- AKeep a counter in
useStateand increment it on click - BAttach an
onClickhandler to a button it renders - CFetch data in
useEffectafter the component mounts - DMake the component
asyncandawaita database query in its body
Show answer
Answer: D (Make the component
asyncandawaita database query in its body)Server Components run only on the server, so they can be
asyncand 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'. - AKeep a counter in
- 21.hard
Why doesn't the button ever become disabled while
saveis running?import { useFormStatus } from 'react-dom'; function EditForm() { const { pending } = useFormStatus(); return ( <form action={save}> <input name="title" /> <button disabled={pending}>Save</button> </form> ); }- A
actionmust be a URL string; with a function, no pending state is tracked - B
pendingis only true for forms submitted withmethod="get" - CThe button needs an explicit
type="submit"for the status to update - D
useFormStatusonly reads a parent form, so it must be called inside the<form>
Show answer
Answer: D (
useFormStatusonly reads a parent form, so it must be called inside the<form>)useFormStatusreads 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 ownSubmitButtoncomponent that calls the hook. Buttons inside a form already default totype="submit". - A
- 22.easy
A parent's state changes. Its child
<Footer />receives no props and isn't wrapped inmemo. DoesFooterre-render?- ANo: React only re-renders children whose props changed
- BOnly if
Footerreads a context value that changed - CYes: when a component renders, React renders its children too by default
- 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 aschildrenfrom a component that doesn't re-render. - 23.mid
What happens when this component mounts?
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} />; }- AIt fetches once on mount and again only when
querychanges - BIt fetches over and over:
optionsis a new object each render, and everysetDatacauses another render - CReact detects the object dependency and skips the effect
- DIt fetches once, because effects with dependencies never re-run on their own
Show answer
Answer: B (It fetches over and over:
optionsis a new object each render, and everysetDatacauses another render)Dependencies are compared with
Object.is, andoptionsis 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 onqueryinstead. - AIt fetches once on mount and again only when
- 24.easy
What does the button show after two clicks?
function Counter() { const [state, setState] = useState({ count: 0 }); function handleClick() { state.count++; setState(state); } return <button onClick={handleClick}>{state.count}</button>; }- A2
- B1
- C0
- DIt throws because state is frozen
Show answer
Answer: C (0)
setState(state)passes the same object reference, soObject.issays 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.mid
A
Profilecomponent keeps a local draft inuseState. The parent switches from user 1 to user 2 by rendering<Profile key={userId} userId={userId} />. What happens?- AThe same instance receives the new
userId; the draft for user 1 stays - BReact unmounts the old Profile and mounts a fresh one, so all its state (and effects) start over for user 2
- C
keyis ignored outside of lists - DReact warns that
keycannot 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
keytells 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. - AThe same instance receives the new
- 26.easy
What is
useIdintended for?- AGenerating stable keys for the items of a rendered list
- BCreating unique ids for new records before they're saved to the database
- CGenerating ids for accessibility attributes that match between server and client
- DReading the
idattribute of the component's root DOM element
Show answer
Answer: C (Generating ids for accessibility attributes that match between server and client)
useIdreturns a stable, unique string per component instance, for attributes likehtmlFororaria-describedby. It is identical in server-rendered HTML and on the client, avoiding the hydration mismatches thatMath.random()would cause. It shouldn't be used for list keys; keys should come from your data.