AtomLearnAtomLearn
DashboardGoalsPathAchievementsReviewSign In
DashboardReactLifting State Up
ReactNot Started

Lifting State Up

Share state between sibling components by moving it to a common parent

0%
Knowledge0%
Learn & Drill
Fluency0%
Drill & Speed
Retention0%
Mastery & Review
Confidence0%
All modes
Practice

Free tier: read the explanation here. Upgrade to Pro for Drills, Speed challenges & Mastery badges.

Upgrade
Knowledge
Fluency
Retention

Knowledge Debt detected

You can study this freely — but your score may plateau if these foundations have gaps. The Mastery badge requires them to be solid.

Explanation

When two sibling components need to share state, to their closest common parent.

Lifting State Up — shared state lives in the common parentParent Componentconst [value, setValue] = useState("")Child A (Input)Child B (Display)onChange={setValue}value={value}calls up ↑Siblings can't share state directly · lift it to their nearest common ancestor · pass setter as a prop callback

Problem: sibling A needs sibling B's state

jsx // Can't access sibling's state directly function FilterInput() { /* has searchTerm state */ } function ResultList() { /* needs searchTerm */ }

Solution: move state up to the parent

jsx function SearchPage() { const [searchTerm, setSearchTerm] = useState(''); // lifted here return ( <> <FilterInput searchTerm={searchTerm} onSearch={setSearchTerm} /> <ResultList searchTerm={searchTerm} /> </> ); } function FilterInput({ searchTerm, onSearch }) { return ( <input value={searchTerm} onChange={e => onSearch(e.target.value)} /> ); } function ResultList({ searchTerm }) { const filtered = allItems.filter(i => i.name.includes(searchTerm)); return <ul>{filtered.map(i => <li key={i.id}>{i.name}</li>)}</ul>; }

The pattern:

  • 1. Remove state from child components
  • 2. Move it to the nearest common ancestor
  • 3. Pass state down as props
  • 4. Pass setter callbacks down as props

This keeps data flow predictable — one source of truth.

Examples

Shared counter between two components

Display reads state; Controls changes it via callback — both via parent

function Parent() {
  const [count, setCount] = useState(0);
  return (
    <>
      <Display count={count} />
      <Controls count={count} onIncrement={() => setCount(c => c + 1)} />
    </>
  );
}

function Display({ count }) {
  return <h2>Count: {count}</h2>;
}

function Controls({ onIncrement }) {
  return <button onClick={onIncrement}>+1</button>;
}

Tab selection lifted to layout

The parent owns which tab is active; both child components receive it as a prop

function Layout() {
  const [activeTab, setActiveTab] = useState('overview');

  return (
    <div>
      <TabBar activeTab={activeTab} onTabChange={setActiveTab} />
      <TabContent tab={activeTab} />
    </div>
  );
}

How well did you understand this?

Next in React

useContext

Continue

Unlocks