<Form> from react-router renders a real <form> and intercepts its submit event. Instead of a full page load, the router serializes the fields into a FormData, calls the matching route's action, revalidates the loaders and re-renders — client-side, but with the browser's own semantics preserved.
<Form method="post" action="/products/new" replace>
<label>Name <input name="name" /></label>
<label>Price <input name="price" type="number" step="0.01" /></label>
<button type="submit" name="intent" value="save">Save</button></Form>method defaults to get, which is no mutation: the fields become a query string on the current route and the loaders re-run — what a search form wants. Any other method routes to the action. action names the route to submit to, replace swaps the history entry instead of pushing one, encType selects application/x-www-form-urlencoded (the default), multipart/form-data or application/json, and navigate={false} turns the submission into a fetcher (useFetcher). The fields are real DOM inputs needing no React 7,897 state: name is the contract with request.formData(), and a submit button carrying its own name/value — the intent pattern above — lets one form drive several branches of an action.
useSubmit() returns an imperative submit(target, options) — target is a form element, a FormData, a URLSearchParams, a plain object or null — for submissions that no button triggers.
function Search() {
const submit = useSubmit(), timer = useRef(null);
function handleChange(event) {
const form = event.currentTarget;
clearTimeout(timer.current);
timer.current = setTimeout(() => submit(form, { replace: true }), 300);
}
return <Form method="get" onChange={handleChange}>
<input type="search" name="q" defaultValue={useLoaderData().q} /></Form>;
}The loader reads new URL(request.url).searchParams.get('q') and returns the matches, so the query lives in the URL and the page is shareable. replace: true keeps 20 keystrokes from becoming 20 history entries, and defaultValue rather than value leaves typing to the browser while the loader catches up. Otherwise prefer <Form>: it keeps working, as a plain HTML form, before JavaScript has loaded.