skip to content

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%

answer

  1. defaults = the values you started with
  2. isDirty is a deep comparison
  3. reset() does not clear errors
  4. success adopts current data as defaults
  5. setDefaults in React, defaults() in Vue

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.

solid answer

~40 s

`useForm` clones the initial values as its **defaults**. `isDirty` is a deep comparison of current data against them, and `reset()` or `reset('email')` restores all or named fields without clearing errors; `resetAndClearErrors()` does both. On **success**, unless your `onSuccess` called `setDefaults` itself, the helper makes the submitted data the new defaults, so `isDirty` drops to false and a later `reset()` returns to the saved values. On a **validation failure** nothing moves: the component stays mounted, data keeps what the user typed, defaults remain the original record, and `isDirty` stays true. You can move defaults yourself with `setDefaults()` in React (`defaults()` in Vue and Svelte), for one field or all current values.

code

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

export default function Edit({ customer }) {
  const form = useForm({ name: customer.name, email: customer.email })

  function save(e) {
    e.preventDefault()
    form.put(`/customers/${customer.id}`)
    // on success the saved values become the new defaults
  }

  return (
    <form onSubmit={save}>
      {/* inputs bound with form.data and form.setData */}
      <button disabled={!form.isDirty || form.processing}>Save</button>
      <button type="button" onClick={() => form.resetAndClearErrors()}>
        Discard changes
      </button>
    </form>
  )
}

go deeper

for a junior

Remember that defaults are the starting values, isDirty compares against them, and reset() puts them back.

for a middle

Explain the success step that adopts submitted data as new defaults, why failure keeps defaults, and why reset() and clearErrors() are separate calls.

for a senior

Use these rules to design honest unsaved-changes warnings and cancel buttons, and know how onSuccess can override the default adoption when a page needs different semantics.

for a principal

Decide team conventions for edit screens, such as when to leave the page after save, how to treat stale props after partial reloads, and which fields never go into history state.

## Defaults are the baseline When you call `useForm({ name: customer.name, email: customer.email })`, **Inertia**'s form helper deep-clones those values and keeps them as the form's **defaults**. Everything about change tracking is measured against that baseline: - **`isDirty`**: `true` when the current data is not deep-equal to the defaults; - **`reset()`**: copies the defaults back into the data; - **`reset('email', 'phone')`**: restores only the named fields (dotted keys reach nested ones). A common use of `isDirty` on a customer edit page is to disable the Save button until something changed, or to warn before leaving the page with unsaved edits. Because the comparison is deep, typing a character and deleting it again returns `isDirty` to `false`. If you pass a **factory function** instead of an object (`useForm(() => ({ ... }))`), `reset()` calls it again, so the defaults are re-read from the component's current props at reset time; in that mode the helper refuses a manual `setDefaults()` call and throws. ## reset() versus clearing errors Resetting values and clearing validation messages are separate operations: | Call | Values | Errors | |---|---|---| | `reset()` | restored to defaults | kept | | `clearErrors()` | kept | removed | | `resetAndClearErrors()` | restored | removed | A frequent bug is calling `reset()` in a Cancel button and leaving stale red messages under inputs that now hold valid, original values. `resetAndClearErrors()` exists for exactly that case, and it also accepts field names. ## What happens after a successful save When the visit succeeds (the controller redirected and the next page has no errors), the helper runs these steps: 1. clears `errors` and sets `wasSuccessful` and `recentlySuccessful`; 2. awaits your own `onSuccess` callback, if you gave one; 3. unless that callback called `setDefaults`, stores a clone of the current data as the **new defaults**. The consequence: straight after saving the customer, `isDirty` is `false`, and `reset()` returns the form to the values just saved, not to the record as it was when the page first loaded. That matches the mental model of an edit form: the saved state is the new clean state. If you want something else, call `setDefaults(...)` inside `onSuccess` and the helper keeps your choice, or call `reset('password')` there to blank a sensitive field. ## What happens after a validation failure A failed validation redirects back to the same page, and Inertia keeps the component mounted for non-GET submissions, so the helper's state survives: - **data** still holds what the user typed; - **defaults** still hold the original record; - **isDirty** stays `true`; - **errors** hold the new messages. Nothing needs repopulating, which is why Inertia apps do not use Blade's `old()` for these forms. ## Moving the defaults yourself The method is named per adapter: | Adapter | Method | |---|---| | React | `setDefaults()` | | Vue 3 | `defaults()` | | Svelte 5 | `defaults()` | Each has three forms: no argument (current data becomes the defaults), a key and a value (one field), or an object (several fields). A typical case is a page that loads a customer, then receives fresher data through a partial reload: calling `setDefaults()` with the new record keeps `isDirty` honest. On the `<Form>` component, the equivalent is the `defaults()` slot method, which takes no arguments, and the `setDefaultsOnSuccess` prop; unlike `useForm`, the component does not adopt new defaults on success unless you ask for it. ## Common mistakes - calling `reset()` for a Cancel button and leaving stale messages under restored inputs; - expecting `reset()` after a save to bring back the record as first loaded, when the saved values are now the defaults; - initialising the form with a factory function and then calling `setDefaults()`, which throws; - passing a partial object to `setData({ email })`, which replaces the whole data object and drops the other fields. ## Remembered forms A **remember key** as the first argument, `useForm('EditCustomer:42', data)`, stores the form's data and errors in browser history state, so pressing Back and Forward restores half-finished edits. `dontRemember('password')` keeps sensitive fields out of that storage. Defaults and `isDirty` work the same way on a remembered form.

  • How do you keep the original record as the reset target even after a successful save?
    Call `setDefaults()` with the original values inside your `onSuccess` callback. The helper records that you set defaults during `onSuccess` and skips its own step of adopting the submitted data, so `reset()` keeps returning to the values you chose.
  • Why would isDirty be false immediately after the user retypes the original email?
    `isDirty` is a deep equality check between current data and defaults, not a flag set by input events. Once every field equals its default again, even after several edits, the comparison finds no difference and reports a clean form.

saying these in an interview costs you the question

  • reset() also clears every validation error on the form
  • After a successful save, reset() returns to the values first loaded
  • isDirty becomes true on the first keystroke and never flips back
  • A failed validation re-initialises the form from server props
  • Vue's form helper exposes setDefaults() exactly like React's