Skip to content
Chapter 44Lesson 8

Quiz - Forms the platform way

Quiz progress

0 / 0

You’re building a bio field with a live 0/280 character counter that updates as the user types. A teammate makes the <textarea> fully controlled — value plus onChange — to drive the count. What’s the lighter move that keeps the field uncontrolled?

Keep defaultValue on the <textarea> (no value prop) and put useState only on the derived count, updating it from onChange. The handler reads the length to set a sibling readout but never writes back into the field, so the DOM still owns the text and it rides through FormData at submit.

The teammate is right — the moment any UI reacts to the field while typing, the input has to be controlled with value and onChange, counter included.

Drop the onChange entirely and read the length from FormData on submit; a live counter isn’t worth any client state.

Given this submitted form, which FormData reads are correct? Select all that apply.

<input name="email" /> // typed "leo@acme.com"
<input type="checkbox" name="subscribe" /> // left unchecked
<input type="checkbox" name="addons" value="sms" /> // checked
<input type="checkbox" name="addons" value="fax" /> // checked

formData.get('subscribe') is null — an unchecked box is absent from FormData entirely, not false or "off".

formData.getAll('addons') is ["sms", "fax"] — repeated names need getAll; get would return only the last value.

formData.get('subscribe') is false — an unchecked checkbox sends the boolean false under its name.

formData.get('addons') is ["sms", "fax"]get returns every value sharing a name as an array.

Your invoice form needs to pass a fixed invoiceId to the action alongside the FormData, so you write <form action={() => updateInvoice(invoiceId, formData)}>. It works in every dev test, then breaks in production for some users. What happened, and what’s the right tool?

The arrow stops React from recognizing a form action, so it loses the build-time rewrite that powers the no-JavaScript POST fallback — the form breaks without JS while still passing every JS test. Use updateInvoice.bind(null, invoiceId) to pre-apply the id and keep a real function reference.

The arrow re-creates the function on every render, causing React to re-submit the form in a loop; memoize it with useCallback to fix the duplicate writes.

There’s no formData variable in scope for the arrow to reference, so it’s undefined at runtime everywhere; define const formData = new FormData() above the return.

A teammate wired a form with const [state, formAction, isPending] = useActionState(createInvoice, null) but put <form action={createInvoice}> on the element. At runtime the invoice still saves on every submit, yet the button never disables and field errors never appear. Which line is the bug?

<form action={createInvoice}> — it must be the bound formAction from the hook. The raw action runs and saves, but never reports back to the hook, so state and isPending stay frozen at their initial values.

useActionState(createInvoice, null) — the initial state can’t be null; seed it with ok(undefined) or the errors won’t render.

The action’s signature — once wrapped, the action must take (formData, prevState), not (prevState, formData).

You extract a SubmitButton so a nested button can read the form’s submit state without prop-drilling. It renders fine but the spinner never shows and the button never disables — no error, no warning. What’s the most likely cause?

useFormStatus() is being called in the form’s own component scope rather than from a descendant rendered inside the <form>. The form broadcasts its state to the subtree below it, so from the owner’s seat pending is false forever.

useFormStatus was imported from react instead of react-dom, which silently returns a stub that’s always false.

The form is missing a method="post", so useFormStatus can’t tell it’s submitting and defaults pending to false.

For which of these mutations is useOptimistic the right reach, rather than a plain pending state? Select all that apply.

A “star/unstar” toggle on an invoice the user is looking at.

A “mark notification as read” tap in a visible list.

A payment submission on a checkout form.

A validation-heavy “create invoice” form with uniqueness and plan-limit checks.

You add useOptimistic to a non-form “star” button. The star flips the instant you click, then immediately snaps back to its old value before the server even responds, and the console logs a warning about an update “outside a Transition or Action.” What did you forget?

The addOptimistic call must run inside a transition. For an imperative onClick there’s no action prop to open one automatically, so you wrap the update and the await in startTransition yourself.

The reducer must return a brand-new array reference; returning the same boolean makes React discard the update after one frame.

The optimistic value needs a client-generated UUID so React can reconcile it by key; without an id the overlay can’t persist.

Sort each invoice rule into where it must be enforced: a constraint attribute mirrored by the schema, schema-only, or the action body only. Which mapping is correct?

“Email is valid” → constraint attribute + schema (mirror); “amount has at most two decimals” → schema only; “invoice number is unique in the org” → action body only.

“Email is valid” → constraint attribute only; “amount has at most two decimals” → constraint attribute + schema; “invoice number is unique in the org” → schema via an async .refine.

All three are mirrors — every rule belongs in both a constraint attribute and the schema, since the schema is the single source of truth.

You want a required email field to show a red border, but only after the user has actually engaged it — not the instant the page loads. Which CSS state do you key the styling off?

:user-invalid (Tailwind’s user-invalid:) — it matches only after the user has edited and left the field or tried to submit.

:invalid (Tailwind’s invalid:) — it’s the standard validity selector and the most direct way to flag a bad field.

A user on slow Wi-Fi submits your <form action={createInvoice}> before the JS bundle finishes hydrating. Which statements about what happens are true? Select all that apply.

The submit still works: the browser issues a native POST to the action’s build-registered URL, and createInvoice runs and redirects on success.

The browser’s constraint checks (required, type="email", pattern) still fire — they’re the one validation layer that survives without JavaScript.

The useActionState inline field errors and the useFormStatus spinner still render, since the action returns the same Result either way.

Nothing happens until hydration completes, because the action prop needs React to intercept the submit.

Quiz complete

Score by topic