Lifting State Up: Share Data Between Siblings Without Chaos
Learn how to move state to a common parent so sibling components can stay simple and in sync.
Introduction
Lifting state up means moving state to the closest common parent of components that need it. This creates a single source of truth so siblings stay in sync.
Why this matters
Two or more components need to read or update the same piece of data. If each component owns its own copy, they’ll drift out of sync. You’ll also end up passing too many callbacks around.
The fix is to move the state up to the closest common parent and pass it down as props. Each child becomes a pure, predictable component.
The problem
A common mistake is letting each sibling keep its own copy of shared data. For example, an input manages its own name, while the display component has no way to access it. The result is drift and duplicated logic.
Inefficient approach
Each component manages its own state, so siblings can’t share data:
import { useState } from "react";
// NameInput owns its own state — siblings can't share it
function NameInput() {
const [name, setName] = useState("");
return (
<div>
<label>Enter your name:</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
);
}
// NameDisplay doesn't know the current name
function NameDisplay() {
return (
<div>
<p>Hello, {}!</p>
</div>
);
}
function App() {
return (
<div>
<h1>Lifting State Up Example</h1>
<NameInput />
<NameDisplay />
</div>
);
}
export default App;
The solution
Lift the name state to the parent (App) and pass it to both children. NameInput becomes a controlled component, and NameDisplay renders whatever it receives.
import { useState } from "react";
function NameInput({ name, setName }) {
return (
<div>
<label>Enter your name:</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
);
}
function NameDisplay({ name }) {
return (
<div>
<p>Hello, {name}!</p>
</div>
);
}
function App() {
const [name, setName] = useState("");
return (
<div>
<h1>Lifting State Up Example</h1>
<NameInput name={name} setName={setName} />
<NameDisplay name={name} />
</div>
);
}
Step-by-step
- Remove local
namestate fromNameInput. - Add
const [name, setName] = useState("")to the parent. - Pass
nameandsetNametoNameInputas props. - Pass
nametoNameDisplayas a prop. - Confirm both children stay in sync through the single source of truth.
Why this is better
- A single source of truth prevents divergence between siblings.
- Data flow is explicit and easy to trace from parent to children.
- Each component has a single responsibility: input updates vs. display.
Edge cases and tips
- If many deeply nested components need the same value, consider Context to avoid prop drilling.
- Keep parents lean—if a parent grows large, extract presentational children or use custom hooks for logic.
- For form inputs, always control both
valueandonChangeto avoid UI drift.
Takeaway
Lift state when multiple components need to stay in sync. Centralizing the source of truth makes your UI predictable and your components easier to test.
Knowledge base
Problem snippet:
function NameInput() {
const [name, setName] = useState("");
return <input value={name} onChange={(e) => setName(e.target.value)} />;
}
function NameDisplay() {
return <p>Hello, {}!</p>;
}
Solution snippet:
function App() {
const [name, setName] = useState("");
return (
<>
<NameInput name={name} setName={setName} />
<NameDisplay name={name} />
</>
);
}