Controlled Components

Difficulty: Intermediate

In React, a controlled component is a form element whose value is driven by React state. The component's displayed value comes from a state variable (via the value prop), and every change to the input is handled by an onChange callback that updates that state. This creates a unidirectional data flow: state determines what the input displays, and user interactions update the state, which in turn updates the input.

The pattern is simple: bind the input's value attribute to a state variable, and bind its onChange to a handler that calls setState with the new value. For text inputs, the event handler typically reads event.target.value. For checkboxes, it reads event.target.checked. For select elements, it reads event.target.value from the selected option. The key is that React state is the single source of truth - the DOM input merely reflects what's in state.

Controlled components give you full programmatic control over form data. You can validate input on every keystroke, transform values (e.g., force uppercase), conditionally prevent changes, synchronize multiple fields, and implement complex form logic. This level of control is not possible with uncontrolled components where the DOM owns the value.

The trade-off is verbosity. Every form element needs a state variable and an onChange handler. For a form with many fields, this means many useState calls and handlers. This boilerplate can be reduced with patterns like a single state object for all fields, a generic handleChange function, or form libraries. But the core pattern remains the same.

Controlled components are the recommended approach in React for most form use cases. They make it straightforward to validate on change, disable the submit button until the form is valid, enforce input formats, and reset form fields. The React documentation explicitly recommends controlled components as the default approach for handling form data.

Code examples

Basic controlled input

function ControlledInput() {
  const [name, setName] = React.useState('');

  const handleChange = (event) => {
    setName(event.target.value);
    console.log('Input changed to:', event.target.value);
  };

  console.log('Current state:', JSON.stringify(name));
  console.log('Input value equals state: true');

  // In JSX: <input value={name} onChange={handleChange} />
  // Simulating a change event:
  handleChange({ target: { value: 'Alice' } });

  return null;
}

ControlledInput();

The input's value is always equal to the state. The onChange handler updates state, which causes a re-render, which updates the input's displayed value. React state is the single source of truth.

Controlled select and checkbox

function FormControls() {
  const [color, setColor] = React.useState('blue');
  const [agreed, setAgreed] = React.useState(false);

  const handleColorChange = (e) => {
    setColor(e.target.value);
  };

  const handleCheckboxChange = (e) => {
    setAgreed(e.target.checked);
  };

  console.log('Selected color:', color);
  console.log('Agreed:', agreed);

  // Simulate interactions
  handleColorChange({ target: { value: 'red' } });
  handleCheckboxChange({ target: { checked: true } });

  console.log('After changes - color: red, agreed: true');
  return null;
}

FormControls();

Select elements use value and onChange like text inputs. Checkboxes use checked instead of value, and event.target.checked instead of event.target.value. Both follow the controlled pattern.

Input transformation in controlled component

function UpperCaseInput() {
  const [text, setText] = React.useState('');

  const handleChange = (e) => {
    // Transform: force uppercase
    const upper = e.target.value.toUpperCase();
    setText(upper);
    console.log('Transformed input:', upper);
  };

  console.log('State:', JSON.stringify(text));

  // Simulate typing 'hello'
  handleChange({ target: { value: 'hello' } });

  return null;
}

UpperCaseInput();

Because we control the value, we can transform user input before storing it. The input always shows the transformed value. This is only possible with controlled components.

Key points

Concepts covered

controlled components, value prop, onChange handler, single source of truth, form state