Progressive enhancement is cited as a benefit of React 19's form Actions. On a server-rendered page that has not yet hydrated, what can a user actually do with such a form, and which parts of the Action experience only start working once hydration finishes?
answer
- the gap between paint and interactive
- forms predate JavaScript
- there has to be somewhere to post to
- hooks have not run yet
- a navigation, not an in-place update
basics
~20 sBefore hydration the browser can still submit the form the ordinary HTML way, so the mutation happens provided the action resolves to a real server endpoint. Everything React layers on — pending UI, optimistic updates, staying on the page — requires hydration.
solid answer
~50 sA form is a native browser control, so the HTML the server sent is already usable: the user can type and press submit before a byte of React has run, and the browser performs a normal POST navigation. That only accomplishes anything if the form's `action` corresponds to something the server can address — in practice a server function your framework exposes at a URL. With a plain client-side function there is no endpoint, so the pre-hydration submission cannot do the work; progressive enhancement is a property of the *setup*, not of the `action` prop by itself. What is definitely unavailable before hydration is everything React adds on top: no `useFormStatus` pending spinner, no optimistic update, no custom client-side validation, no returned state rendered inline, and no staying on the page — the user gets a full navigation instead of an in-place update.
go deeper
Know that a form is a browser primitive: the server-rendered HTML can be submitted before React loads, and that anything React adds on top only appears after hydration.
Explain the condition — the action must correspond to a server endpoint the browser can post to — and list what is missing in the degraded path: pending UI, optimistic updates, inline results, and no navigation.
Show what designing for it costs: every needed value as a named field in the server HTML, a server-rendered error state, and a test that actually submits during the hydration gap.
Decide where the investment belongs. Argue it for public high-abandonment entry points and against it for authenticated internal surfaces, and be explicit about the ongoing maintenance of a second code path.
## The gap the argument is about Between the moment server-rendered HTML paints and the moment React finishes hydrating, the page looks finished and mostly is not. Click handlers do not exist yet, because the components that own them have not run in the browser. On a slow device or a slow network that window can be seconds long, and it is precisely the window in which an impatient user submits a form. Progressive enhancement is the claim that the form works anyway — degraded, but working. It matters most on the pages where abandonment is expensive: sign-in, checkout, search. ## Why a form can work at all without JavaScript Because `<form>` is a browser primitive. Given markup with a method and a destination, the browser serialises the named fields and performs a navigation with them, no scripting involved. That behaviour predates every framework and is still there. React 19's contribution is that its Action model was designed *not to break it*: the same component that gives you pending state and optimistic UI after hydration leaves behind HTML the browser can submit before hydration. The condition is that there must be something to submit *to*. A URL exists when the action is a server function that the framework has published at an address — that is what lets the server receive the fields, run the mutation, and respond with a new document. A purely client-side function has no address; there is nothing for the browser to post to that would perform the work. This is the honest boundary of the React-level answer: React defines the model and the ability of a server function to be referenced from a form, while *how* that reference becomes a routable endpoint, and the CSRF and deployment considerations around it, belong to the framework layer. ## What is lost in the degraded path Everything whose implementation lives in the client runtime: - **Pending UI.** `useFormStatus` is a hook; before hydration no hook has run, so a submit button cannot show a spinner or disable itself. The browser's own loading indicator is the only feedback. - **Optimistic updates.** Same reason — the overlay is produced during a client render. - **The returned result rendered inline.** The state that a `useActionState`-routed Action returns is client state. Pre-hydration, errors have to come back as a re-rendered document from the server. - **Staying on the page.** The whole point of a function action is that React prevents the navigation. Without React there is a navigation, which means scroll position, focus, and any un-submitted client state on that page are gone. - **Custom validation.** Only native constraint attributes — `required`, `type="email"`, `pattern`, `minLength` — are enforced by the browser. Anything you validate in JavaScript is skipped. ## Designing for it, or deciding not to Getting the degraded path right is a real cost, and it is a deliberate choice: - Every field the mutation needs must have a `name` and must be present in the server-rendered HTML — a field whose value lives only in client state does not exist for a native submission. - The server must be able to render an error state as a document, because the inline-error path is unavailable. - You need to test it, which means loading the page with JavaScript disabled or throttled and submitting during the gap. It is not covered by ordinary component tests. For a public sign-up or checkout flow that cost is usually justified. For an authenticated internal dashboard behind a login the user has already hydrated through, it usually is not — and saying so, with the reason, is a stronger answer than reflexively claiming every form must work without JavaScript. ## The sentence to leave the interviewer with Actions do not make a form progressively enhanced by themselves; they make it *possible* for the same form to be both a plain HTML form before hydration and a rich client interaction after it, and whether you get the first half depends on the action having a real server endpoint behind it.
- What must be true of the form's markup for the pre-hydration submission to carry the right data?Every value the mutation needs has to be a named field present in the server-rendered HTML. A value held only in client state, or an input rendered without a `name`, contributes nothing to a native submission — the browser serialises the DOM, not your component state. Hidden inputs are the usual way to carry ids and tokens through that path.
- Is it worth supporting the degraded path on every form in an app?No, and saying so with a reason is the better answer. It pays on public, high-abandonment entry points — sign-in, sign-up, checkout, search — where the hydration gap costs conversions. On an authenticated dashboard the user reached through an already-hydrated app, the extra server-rendered error paths and the testing burden usually outweigh the benefit.
saying these in an interview costs you the question
- Claims any <form action={fn}> works without JavaScript
- Expects useFormStatus pending UI before hydration
- Thinks React queues and replays the pre-hydration submission
- Forgets that the degraded path is a full-page navigation
- Assumes custom client-side validation still runs pre-hydration