Lifting State Up is a React pattern used when multiple components need to share the same state. The state is moved from the individual child components to their closest common parent.
Lifting state up means moving state from a child component to a common parent component so that multiple components can use the same state.
Before:
Parent
├── Child A
│ └── State
└── Child B
After:
Parent
└── State
├── Child A
└── Child B
State should be lifted when two or more components need to stay synchronized.
After lifting state up, the common parent becomes the single source of truth for that piece of state.
Parent
Shared State
/ \
/ \
Child A Child B
Both children receive information from the same state.
Suppose two components each maintain their own temperature value.
function Celsius() {
const [temperature, setTemperature] =
useState("");
}
function Fahrenheit() {
const [temperature, setTemperature] =
useState("");
}
These are two separate states. Changing one does not automatically change the other.
If two components represent the same information but keep separate state values, they can become inconsistent.
Temperature A = 25
Temperature B = 20
The application now has two different values for information that should represent the same temperature.
The solution is to move the state into the closest common parent.
function TemperatureApp() {
const [temperature, setTemperature] =
useState("");
return (
<div>
<CelsiusInput
temperature={temperature}
setTemperature={setTemperature}
/>
<FahrenheitInput
temperature={temperature}
setTemperature={setTemperature}
/>
</div>
);
}
The parent can pass its state to a child through props.
<Child
value={value}
/>
The child can then use the value.
function Child({ value }) {
return (
<p>
Value: {value}
</p>
);
}
If the child needs to change the shared state, the parent can pass a setter function.
<Child
value={value}
setValue={setValue}
/>
The child can then call setValue.
function Child({
value,
setValue
}) {
return (
<button
onClick={() =>
setValue(value + 1)
}
>
Value: {value}
</button>
);
}
The child does not own the state. It requests a change to the state owned by the parent.
Sibling components cannot directly pass state to each other.
Parent
/ \
/ \
Child A Child B
The parent can hold the state and pass data down to both siblings.
Suppose Child A changes a value and Child B needs to display it.
Child A
|
| update
↓
Parent State
|
| value
↓
Child B
This is one of the most common reasons for lifting state up.
function App() {
const [message, setMessage] =
useState("");
return (
<div>
<Input
setMessage={setMessage}
/>
<Display
message={message}
/>
</div>
);
}
Input changes the state while Display reads the same state.
function Input({ setMessage }) {
return (
<input
onChange={e =>
setMessage(
e.target.value
)
}
placeholder="Type something"
/>
);
}
function Display({ message }) {
return (
<h2>
{message}
</h2>
);
}
Whenever the parent state changes, the Display component receives the updated value.
Forms are a common situation where state needs to be shared between components.
function App() {
const [name, setName] =
useState("");
return (
<div>
<NameInput
name={name}
setName={setName}
/>
<Preview
name={name}
/>
</div>
);
}
function NameInput({
name,
setName
}) {
return (
<input
value={name}
onChange={e =>
setName(
e.target.value
)
}
/>
);
}
The parent owns the value while the input receives and updates it through props.
function Preview({ name }) {
return (
<div>
<h2>
Preview
</h2>
<p>
Hello {name}
</p>
</div>
);
}
The Preview component receives the same state that controls the input.
function App() {
const [selected, setSelected] =
useState(null);
return (
<div>
<StudentList
setSelected={setSelected}
/>
<StudentDetails
selected={selected}
/>
</div>
);
}
One child updates the state while another child uses the updated value.
Instead of passing a state setter directly, the parent can define a callback function and pass it to the child.
function App() {
const [selected, setSelected] =
useState(null);
function handleSelect(id) {
setSelected(id);
}
return (
<StudentList
onSelect={handleSelect}
/>
);
}
function StudentList({
onSelect
}) {
return (
<button
onClick={() =>
onSelect(101)
}
>
Select Student
</button>
);
}
The child calls the callback, and the parent updates its state.
A temperature converter is a classic example of lifting state up.
function Calculator() {
const [temperature, setTemperature] =
useState("");
return (
<div>
<TemperatureInput
temperature={temperature}
setTemperature={setTemperature}
/>
<TemperatureResult
temperature={temperature}
/>
</div>
);
}
A shopping cart can be owned by a parent while product and cart components use the shared state.
function App() {
const [cart, setCart] =
useState([]);
return (
<div>
<ProductList
setCart={setCart}
/>
<Cart
cart={cart}
/>
</div>
);
}
A parent can keep the main state and derive other values from it.
const [items, setItems] =
useState([]);
const totalItems =
items.length;
const totalPrice =
items.reduce(
(total, item) =>
total + item.price,
0
);
Child components can receive these derived values as props.
| Lifting State | Context |
|---|---|
| Moves state to a common parent. | Provides values through a Context. |
| Usually works well for nearby components. | Useful for values needed by distant descendants. |
| Data is passed through props. | Components can read the Context value. |
| Simple and explicit. | Can reduce prop drilling. |
| Lifting State | Duplicate State |
|---|---|
| One source of truth. | Multiple copies of related data. |
| Components remain synchronized. | Values can become inconsistent. |
| Updates are centralized. | Updates must be synchronized manually. |
Consider lifting state when:
Not every piece of state needs to be moved upward.
The goal is to find the appropriate owner for each piece of state.
import { useState } from "react";
function App() {
const [name, setName] =
useState("");
return (
<div>
<NameInput
name={name}
setName={setName}
/>
<NamePreview
name={name}
/>
</div>
);
}
function NameInput({
name,
setName
}) {
return (
<input
value={name}
onChange={e =>
setName(
e.target.value
)
}
placeholder="Enter name"
/>
);
}
function NamePreview({ name }) {
return (
<div>
<h2>
Preview
</h2>
<p>
Name: {name}
</p>
</div>
);
}
export default App;
The state is stored in App. NameInput changes it, and NamePreview displays the same state.
Question: What does "lifting state up" mean in React?