React - Controlled Form Components

Forms are a core part of almost every modern web application. From login pages and registration forms to feedback and data-entry systems, handling user input efficiently is critical. In React, form handling is done differently compared to traditional HTML, and one of the most important concepts to understand is Controlled Form Components.

Controlled inputs accept their current value as a prop and a callback to change that value. That implies that the value of the input has to live in the React state somewhere. Typically, the component that renders the input (like a form component) saves that in its state.

1. What Are Controlled Components in React?

A controlled component in React is a form element—such as an input, textarea, select, checkbox, or radio button—whose value is controlled entirely by React state.

In standard HTML, form elements manage their own state internally. React, however, encourages you to store form values in component state and update them using event handlers.

This approach aligns perfectly with React’s unidirectional data flow and component-based architecture.

import ‘./App.css’;
import { useState } from “react”;

function App() {
  const [name, setName] = useState(“”);

  const handleSubmit = (e) => {
    e.preventDefault();
    alert(`Submitted name: ${name}`);
  };

  return (
    <div className=“App”>
      <form onSubmit={handleSubmit}>
        <label htmlFor=“username”>Name:</label>
        <br />

        <input
          id=“username”
          type=“text”
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
        <br /><br />

        <button type=“submit”>Submit</button>
      </form>
    </div>
  );
}

export default App;

 

How it works

  • User types in the input field. Every keystroke triggers the onChange event.
  • onChange handler updates state . The new input value is stored in React state using setState (or a state setter from useState).
  • State change causes a re-rendering. React re-renders the component with the updated state.
  • Input value is controlled by state. The input’s value prop is set from state, so what you see in the input always reflects the current state value.

 

2. Uncontrolled Components

Uncontrolled components are form elements that manage their own state internally, just like standard HTML form elements. Instead of storing input values in React state, the DOM itself remembers what the user typed.
 
Since React does not directly control the value of an uncontrolled input, you cannot access its value using state. You use React ref to access the value..
 
A ref provides a way to directly access a DOM element and retrieve its current value when needed—typically when the form is submitted.
 
Example: Uncontrolled Component Using ‘useRef’
 
import ‘./App.css’;
import { useRef } from “react”;

function App() {
  const inputRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    alert(“Entered Name: “ + inputRef.current.value);
  };

  return (
    <div className=“App”>
      <form onSubmit={handleSubmit}>
        <label htmlFor=“username”>
          Name:
        </label>
        <br />

        <input
          id=“username”
          type=“text”
          ref={inputRef}
        />
        <br /><br />

        <button type=“submit”>Submit</button>
      </form>
    </div>
  );
}

export default App;

 
export default UncontrolledForm;
 
 
How This Works
 
  • The input field does not use the value attribute.
  • The DOM keeps track of the input’s value.
  • useRef() creates a reference to the input element.
  • On form submission, inputRef.current.value retrieves the current input value.
 

3. Difference between Uncontrolled and Controlled Components

Feature            

Controlled Component

Uncontrolled Component

Data stored in

React state

DOM

Value accessed via

‘value’ prop

‘ref’

React re-renders

On every change

Only when needed

Best for

Complex forms

Simple forms

 
**In React, an <input type=”file” /> is always an uncontrolled component because its value is read-only and can’t be set programmatically. 
 

 

4. Why Use Controlled Form Components?

Using controlled components provides several advantages in real-world React applications:
 
1. Full Control Over Form Data – One always knows the exact value of each form field at any moment.
 
2. Easier Form Validation – Validation logic can be applied immediately as the user types.
 
3. Predictable Data Flow – Since data flows from state to UI, debugging becomes simpler.
 
4. Dynamic Form Behavior – One can enable, disable, or modify inputs based on application state.
 
Because of these benefits, controlled components are the recommended approach for handling forms in React.
 
 

5. Handling Multiple Inputs Using a Single State Object

 
RegisterForm.js


import { useState } from “react”;

function RegistrationForm() {
  const [formData, setFormData] = useState({
    username: “”,
    email: “”,
    password: “”,
    bio: “”,
    country: “”,
    gender: “”,
    terms: false
  });

  const [errors, setErrors] = useState({});

  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;

    setFormData({
      …formData,
      [name]: type === “checkbox” ? checked : value
    });
  };

  const validate = () => {
    const newErrors = {};

    if (!formData.username.trim()) {
      newErrors.username = “Username is required”;
    }

    if (!formData.email) {
      newErrors.email = “Email is required”;
    } else if (!/^\S+@\S+\.\S+$/.test(formData.email)) {
      newErrors.email = “Invalid email format”;
    }

    if (formData.password.length < 8) {
      newErrors.password = “Password must be at least 8 characters”;
    }

    if (!formData.bio.trim()) {
      newErrors.bio = “Bio is required”;
    }

    if (!formData.country) {
      newErrors.country = “Please select a country”;
    }

    if (!formData.gender) {
      newErrors.gender = “Please select a gender”;
    }

    if (!formData.terms) {
      newErrors.terms = “You must accept the terms”;
    }

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const handleSubmit = (e) => {
    e.preventDefault();

    if (validate()) {
      console.log(“Submitted Data:”, formData);
      alert(“Form submitted successfully!”);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <h2>Registration Form</h2>

      <br></br>
      {/* Text input */}
      <input
        type=“text”
        name=“username”
        value={formData.username}
        onChange={handleChange}
        placeholder=“Username”
      />
      {errors.username && <p className=“error”>{errors.username}</p>}

      <br></br>
      {/* Email */}
      <input
        type=“email”
        name=“email”
        value={formData.email}
        onChange={handleChange}
        placeholder=“Email”
      />
      {errors.email && <p className=“error”>{errors.email}</p>}

      <br></br>

      {/* Password */}
      <input
        type=“password”
        name=“password”
        value={formData.password}
        onChange={handleChange}
        placeholder=“Password”
      />
      {errors.password && <p className=“error”>{errors.password}</p>}

      <br></br>
      {/* Select */}
      <select name=“country” value={formData.country} onChange={handleChange}>
        <option value=“”>Select Country</option>
        <option value=“India”>India</option>
        <option value=“USA”>USA</option>
        <option value=“UK”>UK</option>
      </select>
      {errors.country && <p className=“error”>{errors.country}</p>}

      {/* Radio buttons */}
      <div>
        <label>
          <input
            type=“radio”
            name=“gender”
            value=“male”
            checked={formData.gender === “male”}
            onChange={handleChange}
          />
          Male
        </label>

        <label>
          <input
            type=“radio”
            name=“gender”
            value=“female”
            checked={formData.gender === “female”}
            onChange={handleChange}
          />
          Female
        </label>
      </div>
      {errors.gender && <p className=“error”>{errors.gender}</p>}

      <br></br>
      {/* Textarea */}
      <textarea
        name=“bio”
        value={formData.bio}
        onChange={handleChange}
        placeholder=“Short Bio”
      />
      {errors.bio && <p className=“error”>{errors.bio}</p>}

      <br></br>
      {/* Checkbox */}
      <label>
        <input
          type=“checkbox”
          name=“terms”
          checked={formData.terms}
          onChange={handleChange}
        />
        I agree to the terms and conditions
      </label>
      {errors.terms && <p className=“error”>{errors.terms}</p>}

      <br></br>
      <button type=“submit”>Register</button>
    </form>
  );
}

export default RegistrationForm;

 
 
App.js

import React from “react”;
import RegistrationForm from “./RegistrationForm”;
function App() {
  return (
    <div style={{ padding: “20px” }}>
      <RegistrationForm />
    </div>
  );
}
export default App;


How This Example Works

1. Form State

const [formData, setFormData] = useState({

  username: “”,

  email: “”,

  password: “”

});

  • It stores all input values in a single state object


2. Controlled Inputs

<input

       type=”text”

        name=”username”

        value={formData.username}

        onChange={handleChange}

        placeholder=”Username”

/>

  • The value comes from React state
  • onChange updates state on every keystroke. This is what makes the form controlled.



3. Single Change Handler

const handleChange = (e) => {

 };

  • It works for all inputs
  • It uses name attribute to update the correct field



4. Field Validation Logic

const validate = () => {

  const newErrors = {};

  // validation rules

};

  • Validation includes : Required field checks, Email format validation, Minimum password length, 
  • Errors are stored in a separate errors state object.


5. Preventing Page Reload

const handleSubmit = (e) => {

  e.preventDefault();

};

  • It prevents browser refresh
  • This allows React to handle form submission logic
 
 
Scroll to Top