How to Build Forms in React

Build a React form with controlled inputs, semantic labels, and a submit handler. Read values, stop the page reload, and show an accessible validation message.

6 min read

A React form collects input through labeled controls, keeps those values in state, and submits them through a single handler. Building React forms comes down to three moves: label every control, track values in state, and stop the browser from reloading on submit. The signup form in this guide shows what appears as you type and what runs when you press Sign up.

Track a field value with state

Start with one controlled field. A controlled input takes its value from state and reports every change back through an onChange handler, so React state is the single source of truth for what the field shows.

App.jsxApp.jsx
import { useState } from "react";
 
export default function NameField() {
  const [name, setName] = useState("");
 
  return (
    <label>
      Full name
      <input value={name} onChange={(e) => setName(e.target.value)} />
    </label>
  );
}

The label wraps the input, so clicking the words Full name focuses the field. Each keystroke calls setName with the new text, and the input re-renders to show exactly what state holds.

Submit the form and prevent the page reload

Wrap your fields in a form element and point its onSubmit prop at a handler. The browser would normally reload the page with the form data, so the first line of the handler calls preventDefault to stop that default navigation.

App.jsxApp.jsx
import { useState } from "react";
 
export default function SignupForm() {
  const [email, setEmail] = useState("");
 
  function handleSubmit(e) {
    e.preventDefault();
    alert(`Signed up with ${email}`);
  }
  // the return below continues this same component

The handler stops the default navigation, then reads the email straight from state. The return block below renders the form and the button that triggers it, still inside SignupForm.

App.jsxApp.jsx
  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="email">Email</label>
      <input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
      <button type="submit">Sign up</button>
    </form>
  );
}

Press the button and the form calls handleSubmit, which stops the reload and reads the current email from state. The button sits inside the form, so clicking it triggers the submit event for free. For a deeper look at why the reload happens and how to stop it, see how to prevent default form submission in React.

Read values with FormData when you do not track state

You do not have to store every value in state. An uncontrolled approach reads the fields at submit time through FormData, which collects every control by its name attribute. This is the pattern the React docs use for simple search and login forms.

App.jsxApp.jsx
export default function SearchForm() {
  function handleSubmit(e) {
    e.preventDefault();
    const data = new FormData(e.target);
    alert(`Searching for ${data.get("query")}`);
  }
  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="query">Search</label>
      <input id="query" name="query" />
      <button type="submit">Search</button>
    </form>
  );
}

Here the input has no value or onChange prop, so the browser DOM owns the text. The handler reads it once from FormData when the form submits. The choice between these two approaches matters enough that controlled vs uncontrolled forms in React is its own guide.

Show a visible validation message

Before you accept a submission, check the value and show a message users can see and hear. Inside the SignupForm above, keep the message in an error state, mark the field with aria-invalid, and connect the message with aria-describedby.

App.jsxApp.jsx
<input
  id="email"
  type="email"
  value={email}
  aria-invalid={error ? "true" : "false"}
  aria-describedby="email-error"
  onChange={(e) => setEmail(e.target.value)}
/>
{error && <p id="email-error" role="alert">{error}</p>}

aria-invalid flags the field for assistive technology, and aria-describedby ties the message to the input so screen readers announce it. The full check and display flow lives in React form validation without a library.

Form submission lifecycle

This flow is the same whether you use state or FormData. The handler always stops the reload first, then validation decides whether to accept the data or show the error. Server validation is the authority, not this client-side check.

Match the control to the data

The form element works with every native control, which makes React forms flexible. Choose the control that captures the data shape, and keep the same label plus state pattern for each one.

ControlUse for
Text inputShort single-line values like a name
Email inputAddresses with a browser format check
SelectOne choice from a fixed list
TextareaMulti-line text like a message
CheckboxA single yes or no value

Checkboxes and radio groups read a checked value instead of text, and selects read value the same way text inputs do. The pattern stays the same: a semantic label, a value in state, and an onChange handler. React 19 also adds an action prop that runs a form submission in a transition and resets uncontrolled fields afterward, which is an alternative to the onSubmit handler used throughout this guide.

Rune AI

Rune AI

Key Insights

  • Give every control a semantic label.
  • Keep a field's value in state for a controlled input.
  • Call preventDefault in the submit handler.
  • Read uncontrolled values with FormData on submit.
  • Validate on submit and show an accessible message.
RunePowered by Rune AI

Frequently Asked Questions

What is a controlled form input in React?

A controlled input takes its value from React state and updates that state through an onChange handler. React state becomes the single source of truth for what the field displays.

Why do I call preventDefault in a form submit handler?

A form submit event reloads the page by default. Calling preventDefault stops that navigation so your handler can read the values and decide what happens next.

Do I need a library to build forms in React?

No. Small forms work fine with plain state, semantic labels, and a submit handler. Libraries like React Hook Form help when forms grow larger.

Conclusion

A React form is labels, state, and a submit handler. Track values in state for live updates, or read them with FormData on submit, and always stop the default page reload.