When would you use Inertia's <Form> component instead of the useForm helper, and how does <Form> gather and submit data?
answer
- inputs identified by name attributes
- reads the DOM through FormData
- method defaults to get
- slot props: errors, processing, isDirty
- useForm for programmatic control
basics
~20 sInertia's <Form> suits plain forms: it reads named inputs from the DOM, submits them as a visit, and exposes errors, processing and isDirty through slot props; useForm suits forms whose data you must compute, control or remember in code.
solid answer
~40 s`<Form action="/customers/42" method="put">` renders a real `<form>`, and on submit reads every named input through the browser's `FormData` (including the clicked submitter), converts it to an object with nested names such as `address[city]` or `address.city` expanded, and sends an Inertia visit through the form helper it wraps. You write uncontrolled inputs (in React, `defaultValue`), and a render function or slot receives `errors`, `processing`, `progress`, `isDirty`, `wasSuccessful`, `reset`, `submit` and more. Props add `transform`, `errorBag`, `disableWhileProcessing` (sets `inert`), `resetOnSuccess`, `resetOnError`, `setDefaultsOnSuccess` and visit `options`. Note that `method` defaults to `get`. Choose `useForm` when data does not live in named inputs, such as custom widgets or computed fields, or when you need `setData`, a remember key or programmatic defaults.
code
jsx · 17 linesimport { Form } from '@inertiajs/react'
export default function Edit({ customer }) {
return (
<Form action={`/customers/${customer.id}`} method="put" disableWhileProcessing options={{ preserveScroll: true }}>
{({ errors, processing, isDirty }) => (
<>
<input name="name" defaultValue={customer.name} />
{errors.name && <p>{errors.name}</p>}
<input name="address.city" defaultValue={customer.address.city} />
{errors['address.city'] && <p>{errors['address.city']}</p>}
<button disabled={!isDirty || processing}>Save</button>
</>
)}
</Form>
)
}go deeper
Recall that <Form> takes action and method, reads inputs by their name attribute, and hands errors and processing to its children.
Explain the FormData read, nested name expansion, the get default for method, the slot members and which props reset or re-baseline the form.
Pick the right tool per screen and spot the traps: a missing method sending GET, isDirty staying true after save, and custom widgets invisible to FormData.
Set a codebase convention for forms, balancing boilerplate against control, and decide how custom inputs expose native names so the simpler component stays usable.
## Two ways to build an Inertia form **Inertia** ships two form APIs in each adapter (React, Vue 3, Svelte 5): - the **`<Form>` component**, which behaves like an HTML form but submits through Inertia; - the **`useForm` helper**, a stateful object you bind inputs to by hand. They share one engine: `<Form>` creates a `useForm` instance internally and drives it from the DOM. So submission, errors, progress and success flags behave the same; the difference is **where the data lives**. ## How <Form> collects data `<Form>` renders a native `<form>` element and intercepts its submit event. On submit it: 1. builds a browser **`FormData`** from the form element, including the submitter button's `name` and `value`; 2. converts it to a plain object, expanding bracket names (`contacts[0][email]`) and dotted names (`address.city`) into nested objects; a backslash escapes a literal dot; 3. applies your `transform` callback, if any; 4. submits with the given `method` and `action` through the internal form helper, so files trigger a multipart body automatically. Because the DOM holds the values, inputs are **uncontrolled**: you give each one a `name`, and in React a `defaultValue` for the current record. There is no `setData`. One trap: the `method` prop defaults to **`get`**, so `<Form action="/customers/42">` without `method="put"` sends the fields as a query string to the GET route. ## What the slot exposes In React the child is a render function; Vue and Svelte pass the same object as slot props. It receives: | Group | Members | |---|---| | request state | `processing`, `progress`, `wasSuccessful`, `recentlySuccessful` | | validation | `errors`, `hasErrors`, `setError`, `clearErrors`, `resetAndClearErrors` | | change tracking | `isDirty`, `defaults()`, `reset()` | | control | `submit()`, `cancel()` | | Precognition | `validate()`, `valid()`, `invalid()`, `validating`, `touch()`, `touched()` | `isDirty` compares the current DOM values with the values captured when the form mounted. `defaults()` takes no arguments and re-captures the current values. Deep children can reach the same object through the `useFormContext()` hook, and a ref exposes it for programmatic calls. ## Props that shape the submission - `transform`: adjust data before sending, such as adding a hidden computed field; - `errorBag`: scope server errors under a named key; - `disableWhileProcessing`: adds the `inert` attribute while the visit runs; - `resetOnSuccess` / `resetOnError`: `true` or a list of field names; - `setDefaultsOnSuccess`: adopt current values as the new baseline after success, off by default; - `options`: visit behaviour after submission, such as `preserveScroll`, `only` or `replace`; - event props: `onSuccess`, `onError`, `onFinish` and the rest of the visit events. ## Common mistakes - leaving out `method="put"` on an edit form and sending a GET; - giving React inputs `value` without `onChange` instead of `defaultValue`, which makes them read-only; - reading `errors['address[city]']` when the server keys nested errors with dots, as `errors['address.city']`; - expecting `isDirty` to drop after a save without `setDefaultsOnSuccess`. ## When useForm is the better tool | Situation | Better fit | |---|---| | plain inputs mapping to request fields | `<Form>` | | a date picker or rich editor without a native input | `useForm` | | values derived from other values as the user types | `useForm` | | saving half-finished input in history with a remember key | `useForm` | | setting specific default values from code | `useForm` | | a very large form where re-rendering per keystroke hurts | `<Form>` | On a customer edit page with name, email and phone inputs, `<Form>` removes most boilerplate. Once the page grows a tag picker or an address autocomplete that is not a real input, `useForm` gives you the explicit state you need.
- How does <Form>'s isDirty differ from useForm's after a successful save?`useForm` adopts submitted data as its new defaults on success, so it reads clean. `<Form>` keeps the values captured at mount unless you set `setDefaultsOnSuccess` or call `defaults()`, so a form that stays on the page after saving can still report dirty.
- How would a nested child component show the form's errors without prop drilling?Call `useFormContext()` inside the child. It returns the parent `<Form>`'s exposed object, or nothing outside a form, giving access to `errors`, `isDirty`, `submit()` and `reset()`.
saying these in an interview costs you the question
- <Form> needs every input bound to state with setData
- <Form> sends a POST when no method prop is given
- <Form> uses a separate submission engine from useForm
- Nested fields require building the object in transform by hand
- <Form> cannot send files without forceFormData