ReactFormsbeginner
Updated:

Controlled Components React Forms

6 min read

Build reliable React forms with controlled components: bind inputs to state, handle many fields, validate input, and submit data the React way.

TL;DR – Quick Answer

A controlled component is a form input whose value is driven by React state rather than the DOM. You set the input's value from state and update that state on every change with an onChange handler, so React is the single source of truth. This makes validation, conditional logic, and submission predictable and easy to reason about.

On This Page

Forms are where most real React work happens: login screens, signup pages, search boxes, checkout flows. React handles forms differently from plain HTML, and the core idea is the controlled component — an input whose value is owned by React state rather than by the browser. Once this clicks, form handling becomes one of the most predictable parts of React.

This guide builds up from a single controlled input to a complete multi-field form with validation and submission. It assumes you are comfortable with useState; if not, the React hooks introduction is the prerequisite.

The two-way binding idea

In plain HTML, an <input> manages its own value internally — you type, and the DOM remembers it. In React, you deliberately take that control away from the DOM and give it to state. The pattern has two halves that must always go together:

  1. Set the input's value from state, so the input displays whatever state says.
  2. Update state in an onChange handler, so typing changes the state.

This creates a tight loop: state drives the input, and the input drives state. React becomes the single source of truth for what is in the field.

import { useState } from "react";

function NameField() {
  const [name, setName] = useState("");

  return (
    <div>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Your name"
      />
      <p>You typed: {name}</p>
    </div>
  );
}

export default NameField;

Every keystroke fires onChange, updates name, and re-renders. Because value={name}, the input always shows the current state. The live <p> proves state and input are perfectly in sync.

Common mistake: Setting value without an onChange. React then locks the input as read-only, because you have told it the value comes from state but given it no way to change that state. Always pair them, or use defaultValue if you truly want the DOM to manage the field.

Controlled vs uncontrolled components

There are two philosophies for form inputs in React, and interviewers frequently ask you to compare them.

Controlled Uncontrolled
Value stored in React state The DOM
Read the value Directly from state With a ref
Update on change onChange handler Not tracked
Best for Most forms, validation Simple or file inputs

A controlled component keeps the value in state, giving you instant access for validation and conditional logic. An uncontrolled component lets the DOM hold the value and you grab it later with a ref — useful for very simple forms or the <input type="file"> element, which must be uncontrolled. For nearly everything else, controlled is the recommended default.

Handling multiple fields with one handler

Real forms have many inputs. Writing a separate handler for each gets tedious fast. The clean solution: store all fields in one state object, give each input a name matching a key, and use a single handler that updates by name.

import { useState } from "react";

function SignupForm() {
  const [form, setForm] = useState({ email: "", city: "" });

  function handleChange(e) {
    const { name, value } = e.target;
    setForm((prev) => ({ ...prev, [name]: value }));
  }

  return (
    <form>
      <input name="email" value={form.email} onChange={handleChange} />
      <input name="city" value={form.city} onChange={handleChange} />
    </form>
  );
}

export default SignupForm;

Two details make this work. The [name]: value computed property updates whichever field changed. And the { ...prev, ... } spread copies the other fields so you do not wipe them — remember, you must never mutate state directly, a rule explained further in the state management overview.

Other input types

Checkboxes, selects, and text areas are all controlled the same way, with a couple of tweaks. A checkbox uses checked instead of value and reads e.target.checked:

<input
  type="checkbox"
  checked={agreed}
  onChange={(e) => setAgreed(e.target.checked)}
/>

A <select> is controlled by putting value on the select element itself, not on the options. A <textarea> in React also uses a value prop rather than putting text between tags as HTML does. Once you internalize "value plus onChange", every input type follows the same shape.

Submitting the form

To handle submission, put an onSubmit on the <form> and call preventDefault to stop the browser's default page reload. Then you have all the data sitting in state, ready to send to an API or validate.

import { useState } from "react";

function ContactForm() {
  const [email, setEmail] = useState("");

  function handleSubmit(e) {
    e.preventDefault();
    console.log("Submitting:", email);
    // send to an API here
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={email} onChange={(e) => setEmail(e.target.value)} />
      <button type="submit">Send</button>
    </form>
  );
}

export default ContactForm;

The e.preventDefault() is essential. Without it, submitting reloads the page and destroys your React state, which is almost never what you want in a single-page app. After a successful submit you often redirect the user, which pairs with the useNavigate hook from the React Router tutorial.

Adding validation

Because the values live in state, validation is just reading that state and deriving an error message. You can validate live as the user types or on submit.

import { useState } from "react";

function EmailField() {
  const [email, setEmail] = useState("");
  const isValid = email.includes("@") && email.includes(".");

  return (
    <div>
      <input value={email} onChange={(e) => setEmail(e.target.value)} />
      {email && !isValid && <p>Please enter a valid email.</p>}
      <button disabled={!isValid}>Continue</button>
    </div>
  );
}

export default EmailField;

Notice how validation becomes trivial: you compute isValid from state, show an error conditionally, and even disable the button. This immediate, expression-driven validation is exactly why controlled components are worth the small extra code, and it uses the conditional rendering patterns from the JSX syntax guide.

Pro tip: For small forms, plain controlled components plus a little validation logic are perfect and worth learning first. For large forms with many rules, teams often reach for a library like React Hook Form to reduce boilerplate — but do not start there. Understanding controlled components manually is what makes those libraries make sense later.

Resetting and pre-filling forms

Because state is the single source of truth, resetting a form is just resetting its state. After a successful submit, set each field back to its initial value and the inputs clear themselves — no DOM manipulation required.

function handleSubmit(e) {
  e.preventDefault();
  saveContact(form);
  setForm({ email: "", city: "" }); // clears the inputs
}

Pre-filling an edit form works the same way in reverse: initialize state with the existing record. If the data arrives from an API after the component mounts, load it in a useEffect and call the setter once it lands. This "state in, state out" symmetry is one of the quiet advantages of controlled components — the same mental model handles empty forms, edit forms, and reset forms without special cases.

Interview note: A common question is "Why does React recommend controlled components?" A strong answer names three things: React state stays the single source of truth, validation and conditional UI become trivial expressions over that state, and resetting or pre-filling is just setting state. Then acknowledge that uncontrolled inputs with refs are the right choice for file inputs and very simple forms.

Common pitfalls to avoid

A few mistakes trip up nearly every beginner. Initializing controlled inputs with undefined instead of an empty string causes React to treat them as uncontrolled and warn you — always start text fields at "". Forgetting the spread when updating an object in state silently drops your other fields. And mutating state arrays or objects directly, then calling the setter with the same reference, often fails to re-render at all.

One more subtle trap: performing heavy work inside the onChange handler. Because it fires on every keystroke, expensive operations like filtering a large list on each change can make typing feel sluggish. When you notice lag, debounce the work or derive it separately rather than running it inline for every character. For most forms this never becomes an issue, but it is worth knowing why a fast-typing user might see a laggy field.

Where to go next

Controlled components are the backbone of every serious React form, so practice by building a real one: a signup form with email, password, a terms checkbox, live validation, and a submit that logs the data. That single exercise exercises state, events, conditional rendering, and submission all at once.

From here, connect your form to a backend and a router to build genuine features — a login flow that validates, submits, and redirects. To see forms inside a complete first project, work through the beginner React tutorial, and revisit the React learning hub for the guided path that places forms alongside hooks, routing, and state management.

Frequently Asked Questions

What is a controlled component in React?
A controlled component is a form element whose value is stored in React state and updated through an onChange handler. React fully controls what the input displays, so the state and the input are always in sync. This is the recommended way to handle forms in React.
What is the difference between controlled and uncontrolled components?
A controlled component keeps its value in React state, so React is the source of truth. An uncontrolled component lets the DOM hold the value, which you read later using a ref. Controlled is preferred for most forms because it makes validation and dynamic behavior straightforward.
Why does my input become read-only when I set value?
If you set the value prop from state but forget the onChange handler, React locks the input because nothing updates the state. Always pair value with an onChange that calls the state setter, or use defaultValue if you intentionally want an uncontrolled input.
How do I handle multiple inputs without writing many handlers?
Give each input a name attribute matching a key in a state object, then use one onChange handler that updates state using the input's name. This lets a single function manage an entire form, keeping the code compact and easy to extend.
How do I stop a React form from reloading the page on submit?
Call event.preventDefault() at the start of your onSubmit handler. By default an HTML form submission reloads the page, which destroys your React app state. preventDefault stops that so you can handle the data in JavaScript instead.

Want to Build Your Career in Java Full Stack with AI?

Join CodeBegun and train with working industry engineers — Explore the Program

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 15 July 2026 LinkedIn
Chat with us