# Controlled Forms and Validation — React

Source: https://www.skillbyai.com/en/react/f-controlled

> State is the source of truth for inputs.

## value plus onChange

A **controlled** input gets its `value` from state and updates that state in `onChange`, so React always knows the current value and can validate, transform (trim, uppercase) or disable buttons as the user types. Call `event.preventDefault()` in the form's `onSubmit` to handle submission in JavaScript. Show validation messages only after the user has typed something, and always validate again on the server: client validation is for convenience, not security.

## User input and structured updates

Controlled inputs make forms predictable; reducers organise complex state changes.

![Three ideas: controlled forms, useReducer, form actions.](assets/figures/react/section-5-map.svg) — Figure 5.1 — Controlled forms, reducers and actions.

## An email form with live validation, run

I ran this with React 19.3 and react-dom 19.3 on Node.js 22, compiling JSX with esbuild 0.28. Browser-style examples use jsdom 29 as a fake DOM (imported from a small setup-dom.mjs file) and act() to apply updates. Typing "asha@" keeps the button disabled and shows "Enter a valid email"; typing a full address enables the button, and clicking it submits the trimmed value. The test sets the input value the way a browser does and fires an input event.

```jsx
import "./setup-dom.mjs";   // test setup: jsdom provides a browser-like DOM in Node
import { useState, act } from "react";
import { createRoot } from "react-dom/client";

function Signup({ onSubmit }) {
  const [email, setEmail] = useState("");
  const valid = /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email);
  return (
    <form onSubmit={e => { e.preventDefault(); if (valid) onSubmit(email); }}>
      <input value={email} onChange={e => setEmail(e.target.value.trim())} />
      <button disabled={!valid}>Sign up</button>
      {!valid && email && <span>Enter a valid email</span>}
    </form>
  );
}
const submitted = [];
const root = createRoot(document.getElementById("root"));
await act(() => root.render(<Signup onSubmit={e => submitted.push(e)} />));
const input = document.querySelector("input");
function type(value) {   // set the value the way a browser does, then fire an input event
  const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
  setter.call(input, value);
  input.dispatchEvent(new window.Event("input", { bubbles: true }));
}
await act(() => type("asha@"));
console.log("button disabled:", document.querySelector("button").disabled, "|", document.querySelector("span")?.textContent);
await act(() => type("asha@example.com"));
console.log("button disabled:", document.querySelector("button").disabled);
await act(() => document.querySelector("button").click());
console.log("submitted:", submitted);
```

Output:

```
button disabled: true | Enter a valid email
button disabled: false
submitted: [ 'asha@example.com' ]
```

## Use a form library for big forms

For many fields, nested data and complex validation, libraries such as React Hook Form with a schema validator (Zod) save a lot of code.

**Quiz:** What makes an input "controlled"?

- [ ] It uses a ref only
- [ ] It has a CSS class
- [ ] It is inside a form tag
- [x] Its value comes from React state and changes go through onChange

*Answer:* Its value comes from React state and changes go through onChange. State is the source of truth.
