skip to content

In a form, what do the touched and dirty flags track, and why does a field's error message wait on one of them?

level: middleimportance: must knowfreq 66%

answer

  1. more state than the value
  2. one is an event, one is a comparison
  3. focus-and-left versus differs-from-initial
  4. gate the message, not the rule
  5. submit attempted touches everything

basics

~20 s

Touched means the field was focused and then left; dirty means its value differs from its initial value. Error display is gated on touched or a submit attempt, so a verdict is not shown before the user has had a turn.

solid answer

~50 s

A form keeps per-field bookkeeping beside the value itself. **Touched** (sometimes visited) flips when the field has been focused and then left - the user's own signal that they consider it finished. **Dirty** (sometimes changed) flips when the current value differs from the initial value, and can flip back if the user retypes the original. The rule's verdict may exist from the first render: a required rule over an empty field is failing before anything happens. What the flags gate is **display**: show an error only if the field is touched, or the form has been submitted, or the field has become dirty - otherwise a freshly loaded form accuses the user of mistakes they have not made. Dirty also drives things unrelated to errors: enabling a save button, warning about unsaved edits, and deciding whether to send a field at all.

go deeper

for a junior

Remember the two words: touched means focused and then left, dirty means changed from the starting value. Errors normally appear once a field is touched or the form has been submitted.

for a middle

Explain the split between running a rule and displaying its verdict, and name the display condition a form actually uses: touched, or submit attempted, sometimes plus dirty.

for a senior

Cover the awkward cases you have hit in production: values arriving late from the server resetting the dirty baseline, structural comparison for non-primitive values, autofill that never sets touched, and flag reset semantics.

for a principal

Decide it once for the organisation: which flag gates display, what the standard reset semantics are, and where that state lives, so every form in the product raises and clears errors at the same moments.

Every form framework ends up keeping more per-field state than the value. Two flags show up in nearly all of them, under slightly different names, because both answer questions the value alone cannot. ## What each flag means - **Touched / visited** - the field has received focus and then lost it at least once. It is set on blur, not on the first keystroke, and it is sticky: once touched, the field stays touched for the life of the form (a reset clears it). It encodes *the user has had a turn at this field*. - **Dirty / changed** - the current value is not equal to the value the field was initialised with. Unlike touched, it is a comparison, not an event log, so it can flip back to clean if the user restores the original value. It encodes *this field carries an edit*. They are independent. A field can be touched and clean (tabbed through, nothing typed), dirty and untouched (filled by a value set from code, or by a browser autofill that never took focus), both, or neither. ## Why the error waits The rule and the message are separate stages: 1. The rule runs and produces a verdict for the current value. 2. The form decides whether that verdict may be shown. Stage two exists because stage one is often correct and unwelcome. On a freshly loaded form, every empty required field is already failing. Showing those verdicts paints the page red before the user has done anything, which reads as a broken form rather than as guidance. So the display condition is usually something like *touched, or a submit has been attempted* - with dirty sometimes substituted for touched on fields where live feedback is wanted. | Condition | Typical use | |---|---| | Not touched, not submitted | Hold the verdict; render the field neutral | | Touched, or submit attempted | Show the field's error if the rule fails | | Dirty | Enable save, warn on navigation away, decide what to send | | Dirty and has an error | Re-check on every change so the fix clears the message | ## The submit-attempted flag Touched alone is not enough, because a user can press submit while several fields were never visited at all. Forms therefore keep a form-level flag for *a submit has been attempted* and treat it as touching everything: at submit, all rules run and all failing fields become eligible to display. Without it, a submit could fail with no visible reason, which is the classic report of a form where the button "does nothing". ## Where the flags come from - The platform's focus events give touched for free: set the flag on the blur of the field. - Dirty needs the initial value retained somewhere, so the framework can compare. That makes it sensitive to what *initial* means - a value arriving late from the server should usually be adopted as the new baseline, otherwise every field is dirty the moment the data lands. - Comparison depth matters for non-primitive values: a field holding a list or an object needs a structural comparison, or it reports dirty on every re-render because a fresh reference is not the same reference. - A value written by code rather than typed produces no focus event, so touched stays false. If display is gated only on touched, a programmatically filled field can hold an error nobody ever sees; gating on *touched or dirty or submitted* covers it. ## What goes wrong - Gating display on dirty alone: an empty required field the user visited and left untouched in value stays silent, because it never became dirty. - Gating on touched alone and forgetting the submit flag: a failed submit leaves untouched failing fields silent. - Never clearing the flags on reset: a reset form still shows errors and still claims unsaved changes. - Treating dirty as a proxy for valid: they answer different questions, and a dirty field is often precisely the invalid one. - Recomputing dirty against the current value instead of the initial one, which makes it permanently true. Frameworks differ in where this state lives. A form library usually owns it per field in a central store; a hand-rolled form keeps a parallel structure beside the values; a runtime with fine-grained tracking can make each flag its own reactive value so only the affected field's message re-renders, while a runtime that re-runs the component function recomputes the whole form's display conditions on each write. The bookkeeping is the same either way - the difference is only how much re-renders when one flag flips.

  • Why is a form-level submit-attempted flag needed when every field already tracks touched?
    Because a user can submit without visiting fields at all. Those fields are untouched, so a display rule based on touched alone keeps their errors hidden and the submit appears to do nothing. The submit-attempted flag makes every failing field eligible to show its message, which is why submit is treated as touching the whole form.
  • A field holds a list value and reports dirty on every render. What is wrong?
    The dirty check is comparing references rather than contents, and a fresh list is a new reference even when its items are identical. Compare structurally, or keep a normalised form of the initial value to compare against. The same trap makes save buttons permanently enabled and unsaved-changes warnings permanently armed.
  • Should the display gate use touched or dirty?
    Touched, plus the submit flag, is the safer base: it covers empty required fields, which never become dirty. Dirty is the better gate for live indicators that describe an edit in progress, and a useful addition for values filled from code or by autofill, which set no focus event and so never become touched.

saying these in an interview costs you the question

  • Uses touched and dirty as if they mean the same thing
  • Shows every required-field error on first render
  • Gates error display on dirty, so empty required fields stay silent
  • Forgets that a failed submit must reveal untouched fields' errors
  • Treats dirty as a synonym for invalid
  • Leaves flags set after a form reset