Skip to content
Chapter 17Lesson 7

Quiz - JSX and HTML semantics

Quiz progress

0 / 0

A teammate keys a list of editable invoice rows by the array index, and it ships:

{rows.map((row, index) => (
<li key={index}>
<input defaultValue={row.label} />
</li>
))}

QA can’t reproduce any bug — the list renders fine, edits save fine. When does this actually break in production?

As soon as a row is deleted, sorted, filtered, or inserted anywhere but the end. React reconciles by key, so a shift in positions makes it reuse the DOM node tied to an index — leaving a half-typed input pinned to a position whose data has changed underneath it.

Immediately on first render — key={index} is a parse-time error because keys must be strings, so the page won’t compile until it’s key={String(index)}.

Only once the list grows past a few hundred rows, where matching by index gets slow enough to drop frames during re-render.

You need a theme provider that uses React state, so you add 'use client' to the top of app/layout.tsx and wrap {children} in it. The app works. Why does a senior reviewer reject this on sight?

The directive flows downward — it turns the layout and every route beneath it into a client subgraph, forfeiting the Server Components default for the whole app at once. The fix is a small <Providers> child that carries 'use client'; a Server Component can render it and pass children through.

'use client' is illegal in any file that renders <html> or <body>, so the build fails in production even though it runs in dev. Move the directive to app/page.tsx instead.

A 'use client' layout can’t accept the children prop, so the pages render blank. The provider has to read the page from a framework hook instead of from children.

Two snippets render identical pixels — same big bold “Billing” text:

<div className="text-2xl font-bold">Billing</div>
<h2 className="text-2xl font-bold">Billing</h2>

A screen-reader user pulls up the page’s heading list. What’s the difference, and what’s the rule it teaches?

The <div> adds nothing to the heading outline — it has no role in the accessibility tree, so a heading jump sails right past it. Only the <h2> is a navigable node. Heading level is a structural claim set by outline position, never by font size: the class carries the look, the element carries the structure.

There’s no functional difference — Tailwind’s text-2xl font-bold gives both an implicit heading role, so a screen reader announces both as level-2 headings.

The <div> is actually more correct: reserving <h2> for text that’s exactly the second-largest on the page keeps the visual hierarchy and the heading hierarchy in sync.

You spot this in a code review:

<button onClick={() => router.push('/dashboard')}>Dashboard</button>

It works when clicked. What does it cost, and what should it be?

It’s a link wearing button clothes — its whole job is to go to a URL, yet it drops everything a real anchor gives for free: middle-click and Cmd-click to open in a new tab, right-click “Copy link”, crawlability, and being announced as a link. Internal navigation belongs in <Link href="/dashboard">, which adds soft navigation on top of a plain <a href>.

Nothing — a button with a router.push is the idiomatic way to navigate in Next.js, and it’s preferable to <Link> because it gives you a place to run logic before navigating.

The only problem is the missing type — add type="button" so it can’t accidentally submit a surrounding form, and it’s correct.

The user submits this form without touching any control. Exactly which entries does the browser pack into FormData?

<form>
<input type="checkbox" name="newsletter" value="yes" defaultChecked />
<input type="checkbox" name="acceptedTerms" value="true" />
<input type="radio" name="billing" value="monthly" />
<input type="radio" name="billing" value="yearly" defaultChecked />
</form>

newsletter=yes and billing=yearly — two entries. A checkbox submits its value only when checked, so the unchecked acceptedTerms contributes nothing (its key is simply absent). The radio group shares one name and submits only the checked member’s value, once.

All three names: newsletter=yes, acceptedTerms=false, and billing=yearly — an unchecked checkbox submits its value with false so the server can read the boolean directly.

newsletter=yes, billing=monthly, and billing=yearly — both radios submit their values, and the server takes the last one under the shared name.

Which of the following are genuine ARIA bugs — markup that misleads or traps a screen-reader user? Select all that apply.

A button whose visible text reads Export carries aria-label="Download CSV".

An <a href="/help"> that a keyboard user can still Tab to carries aria-hidden="true".

An icon-only delete button — no text inside — carries aria-label="Delete invoice".

A decorative chevron glyph rendered beside the word Filters carries aria-hidden="true".

Quiz complete

Score by topic