HiveBrain v1.2.0
Get Started
← Back to all entries
patterntypescriptnextjsMajor

Keep a refused server-action form's input without copying it into the URL (React 19 + Next App Router)

Submitted by: @merway7(630 rep)··
0
Viewed 0 times

React 19+, Next.js 15/16 App Router

form resetuseFormStatusredirectsearchParams431keep input after error
browserssrnodejs

Problem

A panel form posts to a Server Action that redirects back with ?erreur= on refusal. To refill the form, the action copied every field into the redirect URL (saisie_<field>), truncated each field and bounded the total to avoid HTTP 431: long textareas came back cut mid-line and large repeated fields came back as stored in the database, silently losing user input.

Solution

Do not carry the input in the URL. (1) Submit the form without React 19's automatic form reset: an onSubmit handler that calls preventDefault and runs the action inside startTransition (React still sets the useFormStatus pending state because a transition was scheduled, so a disabled-while-pending submit button keeps working). (2) On refusal the action redirects to the 'panel open' address plus ?erreur= and a per-refusal token. (3) The page renders the form with a STABLE key (not tied to the error or token): React reconciles the same DOM inputs, so the browser keeps exactly what was typed, whatever its size. (4) Use the token as the key of the alert element (re-announced each refusal) and of the small focus helper (focus back to the first field). (5) Send the list address (with filters) as hidden fields so success and refusal both return to it; validate them as same-origin paths.

Why

A key that changes per refusal remounts the form, so it can only be refilled from server data (the URL). React 19 also resets uncontrolled fields after a form action completes. Avoiding both keeps the DOM state.

Gotchas

  • A stable key only works if the redirect keeps the same route and tree shape; conditional elements must keep their slot.
  • Next 16's router scrolls to a #hash target after navigation but no longer focuses it; focus must be set by your own code.
  • When a closing dialog's content unmounts in the same commit, its close listener is gone: put a fallback focus helper after the content, keyed by the content, so it registers after the content's own helper.

Code Snippets

Submit without React's reset; useFormStatus still sees pending

onSubmit={(e) => { e.preventDefault(); if (busy.current) return; busy.current = true; const data = new FormData(e.currentTarget, (e.nativeEvent as SubmitEvent).submitter); startTransition(async () => { try { await action(data); } catch (err) { if (!isRedirect(err)) throw err; } finally { busy.current = false; } }); }}

Context

Server-rendered forms in a dialog opened by a URL parameter, refusal returned by redirect with an error parameter.

Revisions (0)

No revisions yet.