Controlled vs Uncontrolled

Who Owns the State?

Controlled vs Uncontrolled

A controlled component takes its value from the parent. An uncontrolled one stores its own. Sometimes both are the right call.

4 min read Level 2/5 #react#patterns#controlled
What you'll learn
  • Build a controlled component
  • Build an uncontrolled one with `defaultValue`
  • Recognize "dual-mode" components

A controlled component takes its value as a prop. The parent owns it. An uncontrolled component manages its own state and exposes a defaultValue for the initial value.

You’ll see this distinction everywhere — <input> is the canonical example.

Controlled

function NameField({ value, onChange }) {
  return <input value={value} onChange={e => onChange(e.target.value)} />;
}

// usage
const [name, setName] = useState("");
<NameField value={name} onChange={setName} />
  • Parent owns the value
  • Parent can validate, transform, reset
  • Every keystroke triggers a re-render in the parent

Uncontrolled

function NameField({ defaultValue, onCommit }) {
  return (
    <input
      defaultValue={defaultValue}
      onBlur={e => onCommit(e.target.value)}
    />
  );
}

// usage
<NameField defaultValue="" onCommit={save} />
  • The input owns the value internally
  • The parent only sees the committed result (on blur, on submit, etc.)
  • Fewer re-renders in the parent

Trade-offs

ConcernControlledUncontrolled
Live validation / formattingEasyHard
External “reset to value X”EasyHard
Re-render costHigherLower
Code at the call siteMoreLess

Dual Mode

A common library pattern: support both. If value is provided, treat as controlled; otherwise, manage internally.

function Toggle({ value, defaultValue = false, onChange }) {
  const [internal, setInternal] = useState(defaultValue);
  const isControlled = value !== undefined;
  const current = isControlled ? value : internal;

  function update(next) {
    if (!isControlled) setInternal(next);
    onChange?.(next);
  }

  return (
    <button onClick={() => update(!current)}>
      {current ? "On" : "Off"}
    </button>
  );
}

Caller can do either:

<Toggle defaultValue={true} onChange={save} />          // uncontrolled

<Toggle value={open} onChange={setOpen} />              // controlled

Up Next

A pre-hooks pattern still worth knowing — render props.

Render Props →