Unit 4: Form Handling and Validation
I. Orientation — Forms as State, Interaction, and Data Boundaries
A React form is an interface for collecting user input, representing it as application data, validating it, and submitting it to another part of the system. Effective form handling depends on clear ownership of state, predictable submission workflows, accessible feedback, and separation between interface logic and validation rules.
- Core data flow: Input generally moves from the user, through form controls, into React state or the browser’s DOM state, and then into a submission handler.
- State ownership: A field may be controlled by React through
valueandonChange, or left under browser control and accessed through a reference. - Validation boundary: Client-side validation gives immediate feedback, but server-side validation remains necessary because browser data can be altered or bypassed.
- Error representation: Errors are commonly stored by field name, such as
{ email: "Invalid email" }, so each message can be rendered beside its field. - Submission lifecycle: A practical form distinguishes states such as
idle,submitting,success, anderror. - Accessibility convention: Every control needs an accessible name, keyboard support, visible focus, and an understandable relationship to instructions and errors.
- Schema principle: A schema describes valid data independently of the visual form, allowing the same rules to support parsing, validation, and type inference.
II. Component State Ownership — Browser-Controlled and React-Controlled Inputs
A. Controlled and uncontrolled components
Controlled and uncontrolled components differ according to whether React state or the DOM is the immediate source of a field’s current value.
- Controlled components:
- Mechanism: React supplies the field’s
value, whileonChangeupdates state after each edit. - Concrete pattern:
- Mechanism: React supplies the field’s
const [name, setName] = useState("");
<input
id="name"
value={name}
onChange={(event) => setName(event.target.value)}
/>- Identifiers:
nameis the current state value;setNameupdates it;event.target.valueis the input’s latest text. - Advantages: Controlled fields support live validation, dependent fields, character counters, and immediate conditional rendering.
- Cost: Every change causes a state update and typically a component render, so very large forms may require careful component separation.
- Important rule: Avoid changing an input from uncontrolled to controlled. Initialize textual values with
"", notundefined.
- Uncontrolled components:
- Mechanism: The browser stores the live value, and React reads it through
FormDataor aref. - Concrete pattern:
- Mechanism: The browser stores the live value, and React reads it through
function submit(event) {
event.preventDefault();
const data = new FormData(event.currentTarget);
console.log(data.get("name"));
}
<form onSubmit={submit}>
<input name="name" defaultValue="" />
</form>- Identifiers:
event.currentTargetis the form;FormDatacollects successful named controls;"name"matches the field’snameattribute. - Advantages: This approach reduces state code and suits simple forms or integration with non-React widgets.
- Limitations: Live cross-field logic is harder because React does not automatically possess the latest value.
- Required case: File inputs are normally uncontrolled because users, rather than application code, select their files.
III. Form Architecture — State, Submission, Reuse, and Interface Behaviour
A. Form state management patterns
Form state should represent values, interaction status, errors, and submission status without creating contradictory sources of truth.
- Individual state variables: Small forms can use
useStateonce per field, such asemailandpassword; this becomes repetitive as fields increase. - Object state: Related values can be stored together and updated with computed property names.
const [values, setValues] = useState({ email: "", password: "" });
function handleChange(event) {
const { name, value } = event.target;
setValues(previous => ({ ...previous, [name]: value }));
}- Update meaning:
previousis the existing object, and[name]updates the property whose key matches the input’sname. - Reducer pattern:
useReduceris useful when events such asCHANGE,BLUR,RESET, andSUBMIT_FAILUREproduce structured transitions. - Interaction metadata:
touched.emailrecords whether the field has been visited;dirty.emailrecords whether its value differs from the initial value. - Derived state: Values such as
isValidshould usually be calculated from current values and errors rather than stored separately.
B. Handling form submission workflows
Submission is a staged process that prevents native navigation, validates data, performs an asynchronous operation, and reports the result.
- Event handling: Attach one
onSubmithandler to<form>so Enter-key submission and submit-button activation follow the same path. - Default behaviour:
event.preventDefault()stops the browser from reloading or navigating before JavaScript processing completes. - Typical workflow:
- Read or use current values.
- Validate and stop if errors exist.
- Set status to
submitting. - Send data with
fetchor another API client. - Handle success or server errors.
- Restore a non-submitting state.
- Duplicate prevention: Disable the submit button while pending, while preserving a visible label such as “Submitting…”.
- Failure handling: Use
try,catch, andfinally; do not clear user-entered values after a recoverable network failure. - Server response mapping: A response such as
{ field: "email", message: "Already registered" }can be placed into the same field-error structure used by client validation.
C. Reusable form component design
Reusable form components should standardize structure and accessibility while allowing the parent form to own domain-specific data.
- Composition: A
FormFieldcomponent can receivelabel,name,error,required, andchildrenrather than assuming one input type. - Stable relationships: Generate or receive an
id, connect<label htmlFor={id}>, and associate help text througharia-describedby. - Controlled interface: A reusable text input commonly accepts
value,onChange,onBlur, anddisabled. - Error interface: Prefer an error message or structured error object over an ambiguous Boolean such as
hasProblem. - Extensibility: Forward ordinary attributes such as
autoComplete,inputMode, andmaxLengthto the native control. - Boundary rule: Keep business rules—for example, a minimum order quantity—outside a generic visual input component.
D. Conditional rendering in form interfaces
Conditional rendering adapts a form to current values or workflow state without creating hidden inconsistencies.
- Dependent fields: Render
companyNameonly whenaccountType === "business". - Validation consequence: When a field disappears, decide explicitly whether to retain, clear, or exclude its value during submission.
- Progressive disclosure: Reveal advanced options only when requested, reducing initial complexity while keeping controls discoverable.
- Status feedback: Render a spinner during submission, a field message after validation, or a success panel after completion.
- Avoid layout ambiguity: Do not hide required information solely with colour or unexplained icons.
- State preservation: Conditional unmounting removes local component state; keeping a component mounted but visually hidden has different accessibility and state effects.
E. Accessibility considerations in form handling
Accessible forms expose names, instructions, states, and errors through both visual presentation and assistive technology.
- Labels: Use an explicit
<label htmlFor="email">Email</label>with<input id="email">; placeholder text is not a substitute. - Grouping: Use
<fieldset>and<legend>for related radio buttons or checkboxes, such as a delivery-method group. - Error association: Connect an invalid field to its message using
aria-describedby="email-error"and setaria-invalid="true". - Announcements: A submission summary may use
role="alert"or an appropriate live region so new feedback is announced. - Keyboard operation: Preserve native controls and logical tab order; avoid positive
tabIndexvalues that create unexpected navigation. - Focus management: After failed submission, move focus to the error summary or first invalid field without trapping the user.
- Input assistance: Attributes such as
autoComplete="email"andinputMode="numeric"help users enter expected data efficiently.
IV. Validation Models — Rules, Patterns, and Schemas
A. Client-side form validation concepts
Client-side validation checks browser-entered data before submission to improve feedback speed and data quality.
- Constraint validation: Native attributes include
required,min,max,minLength,maxLength,pattern, and semantic types such asemail. - Validation timing: Validate on submit for completeness, on blur for timely field feedback, and on change only when immediate guidance is helpful.
- Normalization: Trim surrounding whitespace or standardize case before validation when domain rules permit it.
- Error quality: Messages should identify the problem and remedy, such as “Password must contain at least 12 characters.”
- Security boundary: Client checks are advisory; the server must repeat authorization, integrity, and domain validation.
- UX principle: Do not show an error before the user has interacted unless submission has already been attempted.
B. Commonly used validation patterns
Common validation patterns combine required checks, format checks, range checks, and relationships between multiple fields.
- Presence:
value.trim().length > 0rejects an empty or whitespace-only name. - Length and range: A username may require
3–20characters; an age may require a numeric value between defined limits. - Format: Prefer semantic input types and well-tested validators over overly restrictive regular expressions.
- Confirmation: Password confirmation requires
confirmPassword === password. - Cross-field rules: An end date must be on or after a start date.
- Conditional requirement:
companyNamebecomes required only when the account type is"business". - Asynchronous validation: Username availability requires a server request and should handle delay, cancellation, and stale responses.
- Password caution: Strength rules should encourage length and compromised-password checks rather than arbitrary composition rules alone.
C. Schema-based validation principles
Schema-based validation represents the expected shape and constraints of submitted data in one declarative model.
- Single definition: A schema can specify that
emailis a valid string andageis a positive integer. - Parsing: Schema libraries check unknown input and return validated data, often with transformations or coercion.
- Type inference: In TypeScript, the schema can generate the corresponding static type, reducing mismatch between rules and interfaces.
- Structured errors: Failures normally include paths such as
["address", "postcode"], enabling field-level error mapping. - Cross-field refinement: Object-level rules express relationships such as matching passwords.
- Limit: A schema validates data, not UI interaction state such as whether a field is touched or whether focus should move.
V. Zod Integration — Typed Parsing and Error Handling
A. Form validation using Zod
Zod defines executable schemas that validate unknown data and provide typed results without throwing when safeParse is used.
import { z } from "zod";
const registrationSchema = z.object({
email: z.string().trim().email("Enter a valid email"),
password: z.string().min(12, "Use at least 12 characters"),
confirmPassword: z.string()
}).refine(data => data.password === data.confirmPassword, {
path: ["confirmPassword"],
message: "Passwords must match"
});- Object schema:
z.objectdeclares the required fields and their validators. - String rules:
trim,email, andmin(12)normalize or constrain string values. - Refinement:
refinechecks the relationship between two fields and assigns the failure toconfirmPassword. - Safe parsing:
const result = registrationSchema.safeParse(values);- Result contract:
result.successdistinguishes valid and invalid outcomes; valid data is inresult.data, while failures are inresult.error. - TypeScript support:
type Registration = z.infer<typeof registrationSchema>derives the validated data type from the schema.
B. Integrating schema validation with form logic and error handling
Schema integration connects parsing results to field messages, submission control, and server communication.
- Submission gate: Call
safeParse(values)insideonSubmit; return immediately whensuccessisfalse. - Error flattening:
result.error.flatten().fieldErrorsgroups messages by top-level field names. - Message mapping: Since each field may have multiple messages, an interface may display the first with
fieldErrors.email?.[0]. - Validated payload: Submit
result.data, not the originalvalues, because parsed data may be trimmed, transformed, or coerced. - Error separation: Keep field errors distinct from form-level errors such as “Service unavailable.”
- State cleanup: Clear a field’s old error after successful revalidation, but retain unrelated server errors until their underlying values change.
- Defence in depth: Reuse an equivalent schema on the server where possible, while separately enforcing authentication, authorization, and database constraints.
Did this save you a night before the exam?
LPU Notes is free, and it stays free. Ads cover part of the server bill. The rest comes out of a student's own pocket: the domain, the storage, and keeping the site up through the weeks everyone needs it at once.
The payment button didn't load. An ad blocker or a filtered network is the usual reason. to try again.
Nothing here is ever locked, and nothing unlocks. Chip in only if it was worth it. What it pays for →