Lifting State Up

Course 1 · Ch 10
Lifting State Up
Sharing one piece of state between sibling components by moving it to their common parent

Every state example so far lived inside the one component that used it. The moment two sibling components need access to the same piece of data — and one of them needs to change it — that approach breaks down, since neither sibling can reach into the other's state directly. The fix is always the same: move the state up to their nearest common parent, then pass it back down to both children as props.

The Problem, Concretely

// these two components can't share temperature state as written — // they're siblings, with no direct connection to each other function TemperatureInput() { const [temp, setTemp] = useState(""); // ... } function TemperatureDisplay() { // no way to read TemperatureInput's temp from here }

TemperatureInput and TemperatureDisplay are siblings — neither is the other's parent or child, so neither can read or change the other's state directly. If TemperatureDisplay needs to show whatever value is currently typed into TemperatureInput, the temp state needs to live somewhere both of them can reach.

The Fix — Move State to the Shared Parent

function TemperatureInput({ temp, onTempChange }) { return ( <input value={temp} onChange={(e) => onTempChange(e.target.value)} /> ); } function TemperatureDisplay({ temp }) { return <p>Current: {temp || "--"</p>; } function TemperaturePanel() { const [temp, setTemp] = useState(""); return ( <div> <TemperatureInput temp={temp} onTempChange={setTemp} /> <TemperatureDisplay temp={temp} /> </div> ); }

temp now lives in TemperaturePanel — the closest component that's an ancestor of both siblings. Both children receive it as a prop, but only TemperatureInput is also given setTemp (renamed onTempChange for the callback-prop naming convention from Chapter 4) so it can request a change. Neither child holds its own copy of the state — there's exactly one source of truth, in the parent, and both children stay in sync with it automatically.

This is the same pattern from Chapter 4, just with two children instead of one
"A child calls a function prop the parent gave it" is exactly how TodoItem's delete button worked back in Chapter 4 — lifting state up is that same idea applied to keeping two (or more) components synchronized with each other, rather than just notifying a parent that something happened.

Deciding Where State Should Live

A simple rule of thumb: state should live in the lowest common ancestor of every component that needs to read or change it — no higher, no lower. Putting it too high (e.g. in the very top-level App) when only two deeply nested components actually need it means passing it down through several layers of components that don't care about it at all, just to reach the ones that do.

Prop drilling — a real limitation of this pattern
Passing a prop through three, four, or more layers of components — each one just forwarding it along to its own children, never using it directly — is called prop drilling. It works, but becomes genuinely awkward to maintain as an app grows. Intermediate Chapter 2 introduces the Context API specifically to solve this, letting deeply nested components read a value directly without every component in between having to forward it manually.
SituationWhere state should live
Only one component uses itInside that component itself
Two sibling components need itTheir common parent
Many deeply nested components need itCommon parent + Context (Intermediate Ch 2), to avoid prop drilling

Coding Challenges

Challenge 1

Build a NameInput (a controlled text input) and a NameGreeting (renders "Hello, [name]!") as separate sibling components, both receiving a shared name value lifted up to a common Parent component.

📄 View solution
Challenge 2

Build two sibling Tab buttons and a content area, all controlled by one activeTab state lifted to their shared parent — clicking either button updates which tab's content the content area shows.

📄 View solution
Challenge 3

Build a ColorSwatches component (a row of clickable color buttons) and a PreviewBox component (a div whose background reflects the chosen color) as siblings, with the selectedColor state lifted to their shared parent.

📄 View solution

Chapter 10 Quick Reference

  • Sibling components can't read or change each other's state directly
  • Lift state up — move it to the nearest shared ancestor, pass it (and a setter/callback) down as props
  • Put state in the lowest common ancestor that actually needs it — no higher, no lower
  • Prop drilling — passing a prop through several uninterested layers just to reach a deeply nested consumer
  • Context (Intermediate Chapter 2) solves prop drilling for deeply nested sharing
  • That's React Fundamentals complete — Intermediate picks up with useRef next