Lifting State Up
Learn why two sibling components cannot directly share state, and how moving that state up to their closest common parent solves it.
Introduction
React state is local by default — it belongs to whichever component called useState. That isolation is usually a good thing, but it creates a specific problem the moment two sibling components need to reflect the very same piece of data. Neither sibling can reach into the other's state, because React data only flows one way: from parent to child through props.
Lifting state up is the standard React pattern for this situation: instead of each sibling owning its own copy of the data, the state is moved up to their closest common parent, which then hands it back down to both siblings as props — along with a function they can call to update it. This lesson walks through the problem, the fix, and a complete worked example.
- Why two sibling components cannot directly share state.
- How moving state up to the closest common parent solves this.
- How to pass shared state and an updater function back down as props.
- A full worked example with two inputs reflecting one shared value.
The Problem: Siblings Cannot Share State
Imagine two components, TemperatureInputC and TemperatureInputF, each rendering an input box. If each one manages its own useState independently, typing in one has no effect on the other — even though conceptually they represent the same temperature in two different units.
function TemperatureInputC() {
const [value, setValue] = useState('');
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}
function TemperatureInputF() {
const [value, setValue] = useState('');
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}
function App() {
return (
<div>
<TemperatureInputC />
<TemperatureInputF />
</div>
);
}Typing "100" into the first input
leaves the second input completely unchanged.
The two components have no way to see each other's state.This is not a bug — it is exactly how React is designed to work. State is private to the component that owns it. The fix is not to find a way to "reach across" from one sibling to the other; it is to stop storing the value in either sibling at all.
The Fix: Move State to the Common Parent
The solution is to identify the closest common ancestor of the components that need to share data — here, that is App — and move the useState call there instead. The parent then passes the current value down to both children as a prop, along with a function each child can call to request a change.
Data flows down as props; changes flow up as function calls. Once state lives in the parent, both children are just "views" of the same single source of truth — they can never get out of sync with each other.
Worked Example: Two Synced Inputs
Let's fix the temperature example by lifting the shared value up into App, and passing it down to both inputs as props.
import { useState } from 'react';
function SyncedInput({ label, value, onChange }) {
return (
<label>
{label}:
<input value={value} onChange={(e) => onChange(e.target.value)} />
</label>
);
}
function App() {
// The single source of truth now lives in the parent.
const [sharedValue, setSharedValue] = useState('');
return (
<div>
<SyncedInput label="Input A" value={sharedValue} onChange={setSharedValue} />
<SyncedInput label="Input B" value={sharedValue} onChange={setSharedValue} />
<p>Current value: {sharedValue}</p>
</div>
);
}
export default App;Input A: [ hello ]
Input B: [ hello ]
Current value: hello
(typing in either input updates BOTH instantly)Neither SyncedInput calls useState at all — it simply receives value and onChange as props. Because both instances read from the exact same sharedValue in App, and both call the exact same setSharedValue function, they can never drift out of sync.
When to Lift State (and When Not To)
Lift when siblings must stay in sync
If two or more components must always reflect the same value, that value belongs in their shared parent.
Lift when a parent needs to react to a change
If a parent needs to know when a child's value changes (e.g. to compute a total), the value should live in the parent.
Do not lift state nobody else needs
If only one component ever reads or writes a piece of state, keep it local — lifting it adds unnecessary props.
Do not lift through many layers casually
If the common parent is many levels above, prop drilling becomes painful — that is exactly the problem the next lesson covers.
Common Mistakes
- Leaving a duplicate useState in a child "just in case," which immediately re-introduces the sync bug.
- Passing the value down but forgetting to also pass the updater function, so children have no way to request changes.
- Lifting state higher than necessary — only lift it as far as the closest common parent that actually needs it.
- Mutating the lifted state directly from a child instead of calling the setter function the parent passed down.
Best Practices
- Identify the closest common parent before lifting — do not jump straight to the top-level App component out of habit.
- Name updater props clearly, like onChange or onValueChange, so their purpose is obvious at the call site.
- Keep the lifted state as the single source of truth — child components should be "controlled" and stateless for that value.
- Once state needs to reach many layers down, consider Context instead of continuing to lift and drill props.
- Group related lifted state together in the parent rather than scattering many separate useState calls.
Frequently Asked Questions
Why can't I just use a global variable to share the value instead?
A plain variable outside of React state will not trigger re-renders when it changes, so the UI would not update. Lifted state stays inside React's state system so updates are tracked and rendered correctly.
Does lifting state up affect performance?
Usually not noticeably. The parent and both children re-render when the shared value changes, which is exactly the update you want since both display that value.
What if three or four components need the same state?
The same pattern applies — find their closest common parent and lift the state there. If that parent is very far up the tree, Context (covered in previous lessons) is often a cleaner fit.
Is lifting state up specific to forms?
No. It applies any time sibling components need to share or stay in sync with the same piece of data — forms are simply a common, easy-to-visualize example.
Do class components use the same pattern?
Yes, the concept is identical — state moves to the common parent's this.state, and updater functions are passed down as props. Only the syntax for defining state differs.
Key Takeaways
- Sibling components cannot directly share state because React data flows one way: parent to child.
- Lifting state up means moving shared state to the closest common parent component.
- The parent passes the value down as a prop and an updater function down as another prop.
- Children become controlled views of the parent's state rather than owning their own copies.
- Lift state only as far as necessary — overly aggressive lifting leads to prop drilling.
Summary
Lifting state up turns two independent, out-of-sync components into two views of a single source of truth. By moving useState to the closest common parent and passing the value and its setter down as props, siblings can share data reliably without ever talking to each other directly.
In this lesson, you saw why sibling state cannot be shared directly, learned the lifting pattern, and built two inputs that stay perfectly in sync through a shared parent. Next, you will look at prop drilling — what happens when the common parent sits many layers above the components that need the data.
- You understand why siblings cannot share state directly.
- You can lift shared state up to the closest common parent.
- You can pass state and an updater function down as props.
- You built two inputs that stay in sync through lifted state.