Rendering and data flow

Lift shared state up

Move one piece of state to the nearest common owner when two components must stay in sync.

When two components need the same changing value, move that state to their closest common parent.

function TemperatureCalculator() {
  const [celsius, setCelsius] = useState('')

  return (
    <>
      <TemperatureInput
        label="Celsius"
        value={celsius}
        onChange={setCelsius}
      />
      <p>Fahrenheit: {convertToFahrenheit(celsius)}</p>
    </>
  )
}

Type 100 into the Celsius field and the Fahrenheit line updates from the same owner state.

The parent passes the current value down. The child reports edits through onChange. The converted value is calculated during rendering rather than stored as second state.

This creates one source of truth. Every child receives a snapshot derived from the same owner.

Do not lift every local detail. Whether a dropdown is open may matter only inside that dropdown. Moving all state to the page creates unnecessary prop wiring and broader renders.

Lift state when siblings need coordination or a parent must make decisions from the value. Keep it local otherwise.

Before lifting, each input keeps its own state and they drift apart as you type. After lifting, editing either field updates the shared owner and both stay in sync.

The lifted pattern is sometimes called “controlled components” at the parent level: the parent owns the value, and the child is controlled through props.

The child becomes a controlled component: it receives value and reports changes through a callback instead of keeping its own copy.

Temperature conversion is the classic teaching example because the parent must show two views of one value without storing redundant state.

The parent can also pass a setter function as a callback prop when the child should not know how state is stored.

Build two inputs that edit the same text. First give each local state, then lift it and confirm both always agree.

Lesson completed