Skip to content
intermediate Phase 8 · Forms and Validation

Manual Validation State

Track touched fields, error messages, and cross-field rules without a library.

45m
0 problems
Topic Progress 0%

Manual Validation State: Core Concepts

Separate values, interaction, and errors

Manual validation is easiest to maintain when the form value, interaction state, and validation result are separate pieces of reactive state. The value holds what the person entered. A touched map records whether a field has received meaningful interaction, often defined as losing focus. An errors object maps field names to messages. Keeping these concerns distinct lets the UI avoid showing red errors before the user has had a chance to complete a field, while still validating the complete form before submission.

An error map should have one predictable shape. For a small form, one message per field is often enough; a larger form may keep an array of messages or a richer result with a code. Decide whether an absent key means valid or not yet checked, and do not alternate among empty strings, null, and undefined without a reason. Validation functions should update or clear their own field's error so a corrected value does not keep displaying a stale message.

Field rules and form rules

Field validation checks a value in isolation: whether an email has a plausible format, a required title is present, or a number is in range. Cross-field rules compare multiple values, such as password confirmation, a start date preceding an end date, or one address field being required when another option is selected. Keep each rule close to the data it depends on and revalidate dependent fields when those values change.

A validateField function can return a boolean and update one error entry. A validateAll function calls each field rule, evaluates cross-field constraints, marks fields touched when submission is attempted, and returns one boolean for submit gating. This avoids having separate, slightly different rules in the template and the submit handler. For complex conditional forms, centralize rule selection so hidden fields are not accidentally blocking submission.

Validation timing is a user-experience choice. Validating on every keystroke gives fast feedback but can be noisy, especially for a value that is temporarily incomplete. Validating on blur is calmer, while submit-time validation ensures no rule is skipped. A common pattern is to validate on blur after the first interaction, revalidate while editing an already-invalid field, and always validate everything at submit. Debounce only expensive asynchronous checks; simple local checks should remain immediate.

Errors are part of the control contract

An error is useful only if the person can connect it to the field and understand how to recover. Render a concise message near the input, set aria-invalid when a current error exists, and reference the message with aria-describedby. When submission fails, consider moving focus to an error summary or the first invalid field. Do not rely on red borders or a disabled button as the sole explanation. Keep wording specific: say what input is expected rather than simply saying the value is invalid.

Touched state is not the same as dirty state. Touched means the control was interacted with according to the chosen policy; dirty means its current value differs from a baseline. A field can be touched and unchanged, or changed without blur. Some workflows need both: touched controls when to show errors, while dirty controls whether to prompt before navigation or whether a save is necessary. Avoid overloading one flag with both meanings.

Manual validation is a good fit for a few fields and understandable rules. As nested arrays, conditional branches, localization, asynchronous constraints, and repeated forms accumulate, the manual state machine can become a small framework of its own. At that point a schema or form library can reduce duplication, but it still needs clear presentation, accessibility, and server validation. The core principle remains the same: one source of truth for values, explicit validation timing, and errors that stay synchronized with the rules.

Manual Validation State: Practice

Implement one rule path

The following example validates a signup form without a library. Each field has an explicit rule, confirmation depends on the password, and a watcher rechecks confirmation after the password changes if the user has already interacted with that field. This avoids leaving a misleading success state when an upstream value changes.

<script setup>
import { reactive, watch } from 'vue'

const form = reactive({ email: '', password: '', confirmation: '' })
const touched = reactive({ email: false, password: false, confirmation: false })
const errors = reactive({})

function validateField(name) {
  let message = ''
  if (name === 'email' && !/^\S+@\S+\.\S+$/.test(form.email)) {
    message = 'Enter an email address with a domain.'
  }
  if (name === 'password' && form.password.length < 12) {
    message = 'Use at least 12 characters.'
  }
  if (name === 'confirmation' && form.confirmation !== form.password) {
    message = 'The passwords do not match.'
  }
  if (message) errors[name] = message
  else delete errors[name]
  return !message
}

function submit() {
  for (const name of Object.keys(touched)) touched[name] = true
  const valid = ['email', 'password', 'confirmation']
    .map(validateField)
    .every(Boolean)
  if (!valid) return
  // Send the validated form through the application's API client.
}

watch(() => form.password, () => {
  if (touched.confirmation) validateField('confirmation')
})
</script>

<template>
  <form @submit.prevent="submit">
    <label for="signup-email">Email</label>
    <input id="signup-email" v-model.trim="form.email" type="email"
      @blur="touched.email = true; validateField('email')"
      :aria-invalid="Boolean(touched.email && errors.email)"
      :aria-describedby="errors.email ? 'signup-email-error' : undefined">
    <p v-if="touched.email && errors.email" id="signup-email-error">{{ errors.email }}</p>
    <!-- Add equivalent labeled controls for password and confirmation. -->
    <button type="submit">Create account</button>
  </form>
</template>

The example's regular expression is intentionally modest; it catches an empty or obviously malformed value, but email syntax has edge cases and a valid-looking address may still be unreachable. Use the browser's email input for a useful hint, then let the server own account verification. Password policy should be driven by the product's security requirements, not by a validation example copied without context.

The submit handler marks every field touched before validation so all errors become visible together. A real form can collect invalid names, focus the first corresponding control, and render a summary. If the form has optional fields, include them in the validation design intentionally rather than treating absence as an error by default. If errors are keyed by field name, the template can render each message consistently and tests can assert the mapping.

Refine the interaction policy

Try the form with an empty email. Before blur, no message should appear; after blur, the email rule should explain the problem. Enter a valid-looking value and verify the message clears. Enter a matching password pair, then change the original password: the confirmation error should return because its dependency changed. Finally submit with every field blank and confirm all relevant messages appear. These interactions exercise timing and dependencies, not only the rule functions.

For a real application, avoid placing multiple state mutations and validation calls in a dense template expression. Extract a named handler such as onEmailBlur so the policy can be tested and reused. Decide whether an error should clear immediately while typing or only after a field becomes valid. Clear server errors when the relevant value changes, but do not erase a general network error when editing an unrelated field. Distinguish client rule errors from server responses if the same field can fail for both reasons.

As the form grows, preserve stable field names and ensure each dynamic row has its own error key. For a list of contacts, an error path might include an item id or array index; indices can shift when rows are removed, so an id is safer for long-lived state. Remove errors and touched entries when a row is deleted. This keeps validation state aligned with the visible form rather than accumulating invisible failures.

The practical outcome is a single validation path that supports both immediate feedback and submission gating. The form remains understandable because each state variable has one job and cross-field dependencies are explicit.

Manual Validation State: Trade-offs and Interviews

Avoid stale and contradictory errors

Manual validation becomes unreliable when multiple code paths write the same error with different timing. For example, an input event may clear an error while a submit handler immediately restores it using an older value. Keep validation functions synchronous and deterministic where possible, and make asynchronous checks a separate state with loading, success, and failure outcomes. A username availability request should be associated with the value that triggered it; discard a late response if the user has since typed a different value.

Cross-field rules need dependency management. Password confirmation depends on both confirmation and password; changing either can invalidate the result. Date ranges, country and postal code, or delivery method and address have the same shape. A watcher can revalidate a dependent field, but too many ad hoc watchers make the rule graph hard to see. For a larger form, centralize dependencies or use a schema that validates the whole object and returns paths to affected fields.

Do not confuse client validation with trust. Client checks improve feedback and reduce avoidable requests, but users can bypass them. The server must validate authorization, data types, ranges, and business invariants. If a server returns field errors, map only recognized field paths to visible controls and keep a general message for failures that cannot be attributed to one field. Never render untrusted server text as HTML.

Choosing when to use a library

Manual rules are attractive for a compact form because the logic is visible and no additional abstraction is required. They become costly when forms share nested schemas, conditional sections, arrays of repeated fields, localization, async validation, dirty-state tracking, and server-error mapping. Repeating the same email rule across several components creates drift; building a generic validation engine to avoid that repetition may be more complexity than adopting an established form or schema library.

Compare the cost of the library with the complexity it removes. A schema library can describe value constraints and return structured issues, while a form library can manage interaction state and rendering integration. These are related but distinct responsibilities. A schema does not automatically decide when to show an error, move focus, or preserve input on failure. Keep that presentation policy explicit even when the rule definitions are centralized.

Test behavior, not implementation details

Test that errors do not appear prematurely, appear at the chosen interaction boundary, clear when a field is corrected, and return when a dependent value changes. Verify submit gating and that a valid payload is sent once. Include boundary values and server errors. For async checks, test stale-response handling by resolving requests out of order. Use accessible queries based on labels and error text so the test reflects what a person can perceive.

Touched and dirty are different concepts. Use touched to decide when to reveal a rule failure; use dirty to determine whether the value differs from its initial or last-saved baseline. If users can navigate away, a dirty form may need a confirmation guard even when no validation error exists. Resetting should restore the baseline and clear the associated interaction and error state in one operation.

In an interview, describe values, touched state, errors, and submit state separately. Explain when validation runs, how a cross-field dependency is refreshed, and where server validation remains authoritative. Mention the point at which you would stop hand-building rules and introduce a library. This makes the trade-off concrete: manual validation is not inherently better or worse; it is a fit for a bounded rule set with a clearly owned lifecycle.

Quiz

1. Which statement about Manual Validation State is accurate?

Question 1 options

2. When reviewing Manual Validation State, which point must hold?

Question 2 options

3. Why track whether a field is touched?

Question 3 options

4. What should happen when a password changes after confirmation was validated?

Question 4 options

Flashcards

Question

What is Manual Validation State?

Answer

Track touched fields, error messages, and cross-field rules without a library.

Question

Manual Validation State: why it matters

Answer

A reactive errors object keyed by field name maps each field to its first failure message.

Question

Manual Validation State: key detail

Answer

Marking a field as touched on blur prevents errors from showing before the user interacts.

Question

Manual Validation State: practical note

Answer

Cross-field rules like password confirmation compare two reactive values during validation.

Question

What does touched state control?

Answer

When validation feedback should be shown after user interaction.

Revision Notes

Key Takeaways

  • 1. A reactive errors object keyed by field name maps each field to its first failure message.
  • 2. Marking a field as touched on blur prevents errors from showing before the user interacts.
  • 3. Cross-field rules like password confirmation compare two reactive values during validation.

Interview Tips

  • • Explain Manual Validation State with one concrete example from real vue work, not a textbook restatement.
  • • Discuss the trade-off: when Manual Validation State helps and when a lighter approach wins.
  • • Place Manual Validation State inside the Forms and Validation phase — interviewers check that you see the progression, not isolated facts.

Cheat Sheet

Manual Validation State Cheat Sheet

Phase: Forms and Validation | Level: intermediate

Key points:

  • A reactive errors object keyed by field name maps each field to its first failure message.
  • Marking a field as touched on blur prevents errors from showing before the user interacts.
  • Cross-field rules like password confirmation compare two reactive values during validation.
  • A single validate function returning a boolean drives both per-field errors and submit gating.

Tags: vue, forms, validation

Continue Learning

Before You Start

  • No prerequisites listed.

Related Reading