Lifting State Up
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
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
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.
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.
| Situation | Where state should live |
|---|---|
| Only one component uses it | Inside that component itself |
| Two sibling components need it | Their common parent |
| Many deeply nested components need it | Common parent + Context (Intermediate Ch 2), to avoid prop drilling |
Coding Challenges
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 solutionBuild 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 solutionBuild 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 solutionChapter 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