codejump

AcademyReact

Lifting State Up and Prop Drilling

Why two components that need to stay in sync must share state owned by their closest common ancestor, and when passing that state down several layers becomes a real problem.

3 min readUpdated

3 exercises

Waiting at the end of this page.

The bug: state that should be shared, isn't

JSX
function TemperatureInput() {
  const [value, setValue] = useState('');
  return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}

function Panel() {
  return (
    <>
      <TemperatureInput />
      <TemperatureInput />
    </>
  );
}

Two inputs, each with its own useState call. Type in one and the other does not move — of course it doesn't, each is its own independent closure over its own state variable. useState scopes state to the component instance that called it, and there are two instances here. If the two inputs are meant to represent the same temperature in different units, nothing here connects them.

The fix: lift the state to the closest common ancestor

JSX
function Panel() {
  const [celsius, setCelsius] = useState('');
  return (
    <>
      <TemperatureInput
        value={celsius}
        onChange={setCelsius}
      />
      <TemperatureInput
        value={toFahrenheit(celsius)}
        onChange={(f) => setCelsius(toCelsius(f))}
      />
    </>
  );
}

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

The state moves up to Panel, the one component that is an ancestor of both inputs, and TemperatureInput stops owning any state of its own — it just renders whatever value it's handed and calls onChange when the user types. Now there is exactly one source of truth, and both inputs are views onto it. This is "lifting state up": find the nearest component that is an ancestor of everything that needs to read or write the value, and put the state there instead.

The rule for where to put state in React is the same rule every time: state lives at the lowest common ancestor of everything that needs it, no higher and no lower. Too low, and siblings can't share it. Too high, and every re-render of that state re-renders parts of the tree that never asked to care about it.

Prop drilling: the cost of passing it down

Once state is lifted, everything below it that needs the value gets it as a prop. That's fine for one or two levels. It gets uncomfortable when the value has to pass through components that never use it themselves, purely to reach a child several layers down:

JSX
<Page user={user}>
  <Layout user={user}>
    <Sidebar user={user}>
      <UserMenu user={user} />
    </Sidebar>
  </Layout>
</Page>

Page, Layout and Sidebar each take user only to forward it — none of them reads it. This is prop drilling: correct, typed, easy to follow for a level or two, and increasingly annoying to maintain as more layers and more drilled props pile up. Renaming the prop, or adding a second one that needs the same trip, means touching every component in between even though none of them cares about the value.

What actually signals a problem

Two or three layers of drilling for one prop is not automatically wrong — it's often the simplest thing that works, and simplest should stay the default. The real signal is a prop threaded through four or more components that do nothing with it, or several unrelated props all making the same long trip because they're all needed by the same distant descendant. At that point two escapes exist:

  • Context — skip the intermediate layers entirely for values that are broadly needed (see the Context page), at the cost of every consumer re-rendering when the value changes.
  • Composition — pass components as children or as props instead of drilling the data itself, so Layout receives an already-built <Sidebar> element instead of the pieces Sidebar needs to build itself, and never has to know user exists.

What to say in an interview

State lives at the lowest common ancestor of the components that need it — no lower, or siblings can't share it; no higher, or unrelated parts of the tree re-render for no reason. Prop drilling is the ordinary, acceptable cost of that rule for a level or two; it becomes worth fixing only once a prop is passing through components that do nothing with it themselves, and the fix is Context or composition, not moving the state even higher.

Now practice it

2 challenges · 1 quiz

Reading this page is the cheap half. These are the exercises that make you use it.