devnotes.

Forms & Lifting State Up

Controlled vs uncontrolled inputs

A controlled input's value is driven entirely by React state — the single source of truth is your component, not the DOM:

function NameField() {
  const [name, setName] = useState('');
  return <input value={name} onChange={e => setName(e.target.value)} />;
}

An uncontrolled input lets the DOM keep its own internal state; you read it via a ref only when needed (useful for simple forms or integrating non-React widgets):

function NameField() {
  const inputRef = useRef(null);
  function handleSubmit() { console.log(inputRef.current.value); }
  return <input ref={inputRef} defaultValue="" />;
}

Controlled inputs are the standard approach — they make validation, conditional disabling, and formatting straightforward.

A multi-field controlled form

function RegistrationForm() {
  const [form, setForm] = useState({ name: '', email: '', department: 'CSE' });

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

  function handleSubmit(e) {
    e.preventDefault();
    console.log(form);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" value={form.name} onChange={handleChange} placeholder="Name" />
      <input name="email" value={form.email} onChange={handleChange} placeholder="Email" />
      <select name="department" value={form.department} onChange={handleChange}>
        <option value="CSE">CSE</option>
        <option value="EEE">EEE</option>
        <option value="BBA">BBA</option>
      </select>
      <button type="submit">Register</button>
    </form>
  );
}

Lifting state up

When two sibling components need to share and stay in sync with the same data, move (lift) that state to their closest common parent, then pass it down as props.

function TemperatureApp() {
  const [celsius, setCelsius] = useState(20); // state lives in the parent

  return (
    <div>
      <CelsiusInput value={celsius} onChange={setCelsius} />
      <FahrenheitDisplay celsius={celsius} />
    </div>
  );
}

function CelsiusInput({ value, onChange }) {
  return <input type="number" value={value} onChange={e => onChange(Number(e.target.value))} />;
}

function FahrenheitDisplay({ celsius }) {
  return <p>{(celsius * 9) / 5 + 32}°F</p>;
}

This is the classic pattern behind master-detail views, filter + list combinations, and synced tabs — whenever siblings need the "same" data, it belongs one level up.

Controlled Forms and Lifting State Up in React | DevNotes