skip to content

Form Helper & Error Props

useForm and the <Form> component track submission state, while failed validation returns as an errors prop through the session. Interviewers ask how errors round-trip without JSON.

on this pageshow

explore

questions

6

With Inertia's useForm helper, how do data, setData, processing and errors work together on a customer edit form?

level: juniorimportance: must knowfreq 52%

answer

  1. one stateful object per form
  2. setData(key, value), object or updater
  3. form.put(url) makes an Inertia visit
  4. processing: onStart to onFinish
  5. errors: field name to first message

basics

~20 s

useForm holds the form's fields in data, updates them through setData, and submits them as an Inertia visit with put, post or patch; processing stays true while that visit runs, and errors fills with one message per field when Laravel validation fails.

solid answer

~40 s

`useForm({ name: customer.name, email: customer.email })` returns one object for the whole form. In React the values live in `data` and change through `setData('email', value)`, which also takes an object or an updater function; Vue and Svelte expose the fields directly on the form. `form.put('/customers/42')` (or `post`, `patch`, `delete`, `get`, `submit(method, url)`) makes an Inertia visit with the current data and accepts normal visit options such as `preserveScroll` and `onSuccess`. `processing` becomes `true` when the request starts and, in Inertia 3, returns to `false` only in `onFinish`, so binding the button's `disabled` to it blocks double submits. When validation fails, `errors` becomes an object keyed by field holding the first message each, `hasErrors` turns true; on success `wasSuccessful` and the two-second `recentlySuccessful` flag drive a saved notice.

code

jsx · 24 lines
jsx
import { useForm } from '@inertiajs/react'

export default function Edit({ customer }) {
  const { data, setData, put, processing, errors, recentlySuccessful } = useForm({
    name: customer.name,
    email: customer.email,
  })

  function submit(e) {
    e.preventDefault()
    put(`/customers/${customer.id}`, { preserveScroll: true })
  }

  return (
    <form onSubmit={submit}>
      <input value={data.name} onChange={(e) => setData('name', e.target.value)} />
      {errors.name && <p>{errors.name}</p>}
      <input value={data.email} onChange={(e) => setData('email', e.target.value)} />
      {errors.email && <p>{errors.email}</p>}
      <button disabled={processing}>Save</button>
      {recentlySuccessful && <span>Saved.</span>}
    </form>
  )
}

go deeper

for a junior

Recall the shape: useForm with initial values, setData to change a field, put or post to submit, errors to show messages, processing to disable the button.

for a middle

Explain the lifecycle behind processing and progress, why errors arrive keyed by field with one message each, and how setData's object and updater forms differ.

for a senior

Show you know a form submission is a visit, not a request you await: outcomes arrive through callbacks and props, and the Inertia 3 onFinish timing affects double-submit guards.

for a principal

Weigh the helper's built-in state against a separate client-side form library, considering duplicated validation logic, consistency across teams and the cost of two state models.

## What useForm is **Inertia** lets a Laravel controller return a JavaScript page component (React, Vue or Svelte) instead of a Blade view, and turns link clicks and form submissions into XHR **visits** that swap the page without a full reload. The **`useForm`** helper, imported from your adapter (`@inertiajs/react`, `@inertiajs/vue3`, `@inertiajs/svelte`), is the stateful object you build a form around. You pass it the initial values, and it gives back everything a form needs: - the current field values; - a way to change them; - submit methods that send an Inertia visit; - request state (`processing`, `progress`); - validation state (`errors`, `hasErrors`); - success flags (`wasSuccessful`, `recentlySuccessful`). For a customer edit page the initial values usually come from the controller's props: `useForm({ name: customer.name, email: customer.email, phone: customer.phone })`. ## Reading and writing fields In React the values sit in `form.data` and are immutable React state, so you change them through **`setData`**. Vue and Svelte expose each field directly (`form.email`) and you bind inputs with `v-model` or `bind:value`. | React call | Effect | |---|---| | `setData('email', value)` | sets one field (dotted keys such as `'address.city'` reach nested values) | | `setData({ name, email, phone })` | replaces the whole data object | | `setData(prev => ({ ...prev, phone: '' }))` | computes the next data from the previous data | Because the object owns the values, you do not keep a parallel `useState` per input, and the helper can compare them with the initial values for `isDirty`. ## Submitting The form object has **`get`, `post`, `put`, `patch` and `delete`**, each taking a URL and optional visit options, plus `submit(method, url, options)`. `form.put('/customers/42', { preserveScroll: true })` sends a PUT visit whose body is the current data (after any `transform()` callback). It is still an Inertia visit, not a `fetch` you await: the Laravel controller answers with a redirect or an Inertia page, and the client swaps props. Callbacks such as `onSuccess`, `onError` and `onFinish` are how you react to the outcome. ## The request lifecycle and processing The helper wraps the visit's events and updates its own state: 1. `onBefore` clears `wasSuccessful` and `recentlySuccessful`. 2. `onStart` sets **`processing`** to `true`. 3. `onProgress` fills **`progress`** while a file upload is in flight (it stays `null` otherwise). 4. `onSuccess` or `onError` updates the success flags or the errors. 5. `onFinish` sets `processing` back to `false` and `progress` back to `null`. Since **Inertia 3**, that reset happens only in `onFinish`; earlier releases cleared `processing` as soon as a response arrived. The practical rule is unchanged: bind the submit button's `disabled` attribute to `processing` and a second click cannot fire a second PUT while the first visit is still being handled. ## errors, hasErrors and success flags When Laravel validation fails, the Inertia adapter delivers the messages as page props, and the helper copies them into **`errors`**: a plain object keyed by field name, with the **first message** per field as a string (the server middleware can switch that to arrays). Related members: - **`hasErrors`**: `true` when `errors` has any key; - **`clearErrors()`** / `clearErrors('email')`: removes all or some messages, for example when the user edits the field; - **`setError('email', 'Already taken')`**: sets messages yourself, for client-side checks; page props stay untouched; - **`wasSuccessful`**: `true` after the last submission succeeded; - **`recentlySuccessful`**: `true` for 2000 ms after success (configurable as `form.recentlySuccessfulDuration` in the app defaults), made for a brief "Saved" label. These errors are scoped to the form object that submitted, so a second `useForm` on the same page does not show them. ## Putting it together ```jsx const form = useForm({ name: customer.name, email: customer.email }) function submit(e) { e.preventDefault() form.put(`/customers/${customer.id}`, { preserveScroll: true }) } ``` The input for `email` reads `form.data.email`, writes with `form.setData('email', e.target.value)`, shows `form.errors.email` when present, and the button uses `disabled={form.processing}`. After a failed save, the typed values remain because the component stays mounted; after a successful save, `recentlySuccessful` shows the confirmation and the helper adopts the saved values as its new defaults.

  • Besides a key and a value, what does React's setData accept?
    A whole object, which replaces the form data, or an updater function that receives the previous data and returns the next object. The updater form is the safe choice when the new value depends on the old one, for example clearing `phone` when a `no_phone` checkbox is ticked, because it never reads a stale render's data.
  • How do useForm's errors differ from page.props.errors on the same page?
    `page.props.errors` is the global prop the Laravel adapter shares on every response. The helper copies the errors of its own visit into `form.errors`, so a second form on the page stays clean. `setError` only changes the form object, never the page props, which makes it the tool for client-side checks.
  • Why bind the button to processing instead of a local useState flag?
    `processing` follows the visit's real lifecycle: it turns on in `onStart` and off in `onFinish`, which also fires after a cancel or a network error. A hand-rolled flag has to be reset on every one of those paths, and a missed path leaves the button disabled forever or lets a double submit through.

saying these in an interview costs you the question

  • useForm is a fetch wrapper whose promise resolves with the controller's JSON
  • processing must be switched on and off by hand in the callbacks
  • errors holds every message for each field as an array by default
  • Calling setError also rewrites page.props.errors
  • In Inertia 3, processing drops to false the moment response headers arrive
open as a page

With Inertia and Laravel, how do failed validation errors reach the page component as props without a 422 JSON response?

level: middleimportance: must knowfreq 55%

basics

~20 s

Laravel treats the Inertia visit as a normal form post: validation failure redirects back with errors flashed to the session, and on the follow-up request the Inertia middleware shares them as the always-included errors prop, which the client routes to onError.

open as a page

When would you use Inertia's <Form> component instead of the useForm helper, and how does <Form> gather and submit data?

level: middleimportance: should knowfreq 30%

basics

~20 s

Inertia'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.

open as a page

In Inertia's useForm, how do isDirty, reset() and the form's defaults behave after a customer edit saves or fails validation?

level: middleimportance: should knowfreq 34%

basics

~20 s

isDirty compares the current data with the form's defaults and reset() restores them; after a successful save useForm adopts the submitted data as the new defaults, while a failed validation leaves defaults unchanged, so the form stays dirty.

open as a page

Why does a customer avatar sent with Inertia's form.put() arrive empty in a Laravel controller, and how do you fix it?

level: seniorimportance: should knowfreq 36%

basics

~20 s

A File in the form data makes Inertia send multipart/form-data, and PHP only parses multipart bodies for POST, so a real PUT reaches Laravel with no fields or files; submit with post() and _method: 'put' so Laravel spoofs the method.

open as a page

How does Inertia 3's built-in Precognition give a customer form live validation with validate() and invalid(), and what must the Laravel route provide?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Precognition sends the form to its real route with a Precognition header; Laravel's precognitive middleware runs validation from the route's form request but not the controller, answering 204 or 422, and useForm's validate() and invalid() show the result per field.

open as a page