As React applications become larger, you will often have multiple components that need to work with the same piece of information.
For example, imagine a page with two components:
App
/ \
↓ ↓
Search Results
The Search component allows the user to enter a search term, while the Results component needs that search term to display matching results.
If both components maintain separate copies of the search term, they can easily become out of sync.
React provides a simple solution: lift the shared state up to their closest common parent.
This technique is called lifting state up.
The basic idea is:
Parent
/ \
↓ ↓
Child A Child B
↑ ↓
updates receives
│ │
└─ shared state ─┘
The parent becomes the owner of the shared state. It can pass the current state to one or more children through props and pass callback functions when children need to request updates.
What Does Lifting State Up Mean?
Lifting state up means moving state from a child component to a common parent component so that multiple components can access and coordinate that state.
Consider two components:
App
├── ComponentA
└── ComponentB
Suppose both components need access to the same value.
Instead of:
ComponentA
└── own state
ComponentB
└── separate state
the state can be moved into App:
App
│
Shared State
/ \
↓ ↓
ComponentA ComponentB
Now both components work with the same source of truth.
Why Is Lifting State Up Important?
Without lifting state, components that need the same information may maintain separate copies.
For example, imagine a temperature converter with two inputs:
TemperatureApp
/ \
↓ ↓
CelsiusInput FahrenheitInput
If CelsiusInput owns the temperature and FahrenheitInput has its own separate state, changing one input will not automatically update the other.
Instead, the parent can own the temperature:
TemperatureApp
│
temperature
/ \
↓ ↓
Celsius Fahrenheit
When one child changes the temperature, the parent updates the shared state and passes the new value to both children.
This keeps the two inputs synchronized.
When Should You Lift State Up?
You should consider lifting state when:
- Two or more components need the same state.
- One component needs to affect another component.
- Multiple components need to stay synchronized.
- A parent needs to coordinate data between children.
- You want one source of truth for related data.
You do not need to lift every piece of state.
If only one component uses a piece of state, keeping it inside that component is usually simpler.
The goal is to place state where it can be shared without unnecessarily making unrelated components depend on it.
The Basic Pattern
Lifting state up usually follows a simple pattern.
Move State to the Common Parent
Instead of storing the state in one child:
function ChildA() {
const [value, setValue] = useState("");
}
move it into the parent:
function App() {
const [value, setValue] = useState("");
}
Pass the Value Down
The parent passes the current value to the children:
<ChildA value={value} />
<ChildB value={value} />
Pass a Function for Updates
If a child needs to change the value, pass a callback:
<ChildA
value={value}
onChange={setValue}
/>
The child can then request a change:
function ChildA({ value, onChange }) {
return (
<input
value={value}
onChange={(event) => onChange(event.target.value)}
/>
);
}
The parent remains the owner of the state.
A Simple Example
Let’s create two sibling components that display and update the same name.
import { useState } from "react";
function App() {
const [name, setName] = useState("");
return (
<div>
<NameInput
name={name}
onNameChange={setName}
/>
<NamePreview name={name} />
</div>
);
}
function NameInput({ name, onNameChange }) {
return (
<input
value={name}
onChange={(event) => onNameChange(event.target.value)}
placeholder="Enter your name"
/>
);
}
function NamePreview({ name }) {
return (
<h2>Hello, {name || "Guest"}!</h2>
);
}
export default App;
The component structure is:
App
/ \
↓ ↓
NameInput NamePreview
The state lives in App:
const [name, setName] = useState("");
NameInput receives the value and update function:
<NameInput
name={name}
onNameChange={setName}
/>
NamePreview receives the current value:
<NamePreview name={name} />
Understanding the Example Step by Step
The first step is moving the state into the parent.
const [name, setName] = useState("");
The parent now controls the value.
Next, the parent passes the value to the input:
name={name}
The parent also passes the state updater:
onNameChange={setName}
When the user types, the child calls:
onNameChange(event.target.value)
The parent’s state changes.
React then renders the parent again.
The updated value is passed to NamePreview:
<NamePreview name={name} />
The preview automatically displays the new value.
The flow is:
User types
↓
NameInput
↓
onNameChange(value)
↓
App
↓
setName(value)
↓
name state changes
↓
NamePreview receives new name
Lifting State Up with a Temperature Converter
The classic example of lifting state up is a temperature converter.
The application contains:
Calculator
/ \
↓ ↓
CelsiusInput FahrenheitInput
Both inputs represent the same temperature.
The parent should own the temperature value and scale.
import { useState } from "react";
function Calculator() {
const [temperature, setTemperature] = useState("");
const [scale, setScale] = useState("c");
const celsius =
scale === "c"
? temperature
: toCelsius(temperature);
const fahrenheit =
scale === "f"
? temperature
: toFahrenheit(temperature);
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={(value) => {
setScale("c");
setTemperature(value);
}}
/>
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={(value) => {
setScale("f");
setTemperature(value);
}}
/>
</div>
);
}
function TemperatureInput({
scale,
temperature,
onTemperatureChange
}) {
const label =
scale === "c"
? "Celsius"
: "Fahrenheit";
return (
<fieldset>
<legend>Enter temperature in {label}:</legend>
<input
value={temperature}
onChange={(event) =>
onTemperatureChange(event.target.value)
}
/>
</fieldset>
);
}
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return celsius * 9 / 5 + 32;
}
The important concept is not the conversion formula.
The important concept is that both inputs depend on the same underlying state.
One Source of Truth
The temperature example demonstrates an important React principle:
Avoid maintaining multiple independent copies of the same state when the values are supposed to represent the same thing.
Instead of:
CelsiusInput
temperature = ...
FahrenheitInput
temperature = ...
the parent coordinates the value:
Calculator
│
Shared temperature
/ \
↓ ↓
Celsius Fahrenheit
This makes synchronization much easier.
Lifting State Up with a Search Box
Consider a product search page:
App
/ \
↓ ↓
SearchBox Results
The search box changes the search term.
The results need the same search term.
The parent can own the state:
function App() {
const [searchTerm, setSearchTerm] = useState("");
return (
<>
<SearchBox
value={searchTerm}
onChange={setSearchTerm}
/>
<Results searchTerm={searchTerm} />
</>
);
}
The search component:
function SearchBox({ value, onChange }) {
return (
<input
value={value}
onChange={(event) => onChange(event.target.value)}
placeholder="Search..."
/>
);
}
The results component:
function Results({ searchTerm }) {
return (
<p>
Results for: {searchTerm || "All Products"}
</p>
);
}
The parent coordinates both components.
Lifting State Up with a Shopping Cart
A shopping cart is another realistic example.
Consider:
App
/ \
↓ ↓
ProductList Cart
ProductList needs to add products.
Cart needs to display products.
The shared cart state can live in App:
function App() {
const [cart, setCart] = useState([]);
function addToCart(product) {
setCart((currentCart) => [
...currentCart,
product
]);
}
return (
<>
<ProductList onAddToCart={addToCart} />
<Cart items={cart} />
</>
);
}
The product list receives the callback:
function ProductList({ onAddToCart }) {
const product = {
id: 1,
name: "Wireless Mouse",
price: 799
};
return (
<button onClick={() => onAddToCart(product)}>
Add to Cart
</button>
);
}
The cart receives the shared data:
function Cart({ items }) {
return (
<div>
<h2>Cart</h2>
{items.map((item) => (
<p key={item.id}>
{item.name} - ₹{item.price}
</p>
))}
</div>
);
}
The state has been lifted to the common parent because both children need to participate in the same data flow.
Lifting State Up with Checkboxes
Another common example involves selecting items.
Suppose a parent renders:
App
/ \
↓ ↓
Filters Results
The Filters component controls which category is selected.
The Results component needs to know the selected category.
The parent can own:
const [category, setCategory] = useState("All");
Then:
<Filters
category={category}
onCategoryChange={setCategory}
/>
<Results category={category} />
The filter component updates the parent state.
The results component receives the new value.
Lifting State Up Does Not Mean Moving Everything to App
A common misunderstanding is that lifting state up means moving all state to the root component.
It does not.
The goal is to move state to the closest common parent that needs to coordinate it.
Consider:
App
├── Header
├── Dashboard
│ ├── Search
│ └── Results
└── Footer
If only Search and Results need the search term, the state might belong in Dashboard, not necessarily in App.
App
└── Dashboard
│
├── Search
└── Results
This keeps the state closer to the components that use it.
State Ownership
A useful question when designing React components is:
Which component should own this state?
Consider:
App
├── Header
├── Profile
└── Settings
If only Settings needs a temporary form value, that state can remain inside Settings.
If both Profile and Settings need the same user preference, their common parent might need to own it.
If many unrelated components across the application need the value, Context or another state-sharing approach may be appropriate.
State ownership should be based on who needs the data.
Controlled Components and Lifting State
Lifting state up is closely connected to controlled components.
A controlled input receives its value from React state:
<input
value={name}
onChange={handleChange}
/>
When the state lives in the parent, the parent controls the input.
For example:
function App() {
const [name, setName] = useState("");
return (
<NameInput
value={name}
onChange={setName}
/>
);
}
The child input becomes controlled by the parent.
This makes the current value available to other components that receive the same state.
Lifting State Up and Derived Data
Once shared state is stored in one place, other values can often be calculated from it.
For example:
function App() {
const [items, setItems] = useState([]);
const totalItems = items.length;
return (
<>
<ItemList items={items} />
<ItemSummary count={totalItems} />
</>
);
}
There is no need to maintain:
const [totalItems, setTotalItems] = useState(0);
if the count can simply be derived from items.
This reduces the number of state values that need to stay synchronized.
Lifting State Up and Immutable Updates
When lifted state contains arrays or objects, update them immutably.
For an array:
setItems((currentItems) => [
...currentItems,
newItem
]);
For removing an item:
setItems((currentItems) =>
currentItems.filter((item) => item.id !== itemId)
);
For updating an object:
setUser((currentUser) => ({
...currentUser,
name: "Karim"
}));
These patterns create new values instead of directly modifying the existing state.
Common Mistakes
Lifting State Too High
Moving state all the way to the root when only two nearby components need it can make the component structure unnecessarily complicated.
Prefer the closest common parent.
Keeping Duplicate State
If two components represent the same piece of information, maintaining separate state values can cause synchronization problems.
Look for an opportunity to have one source of truth.
Mutating Shared State
Avoid:
items.push(newItem);
when items is React state.
Use:
setItems((currentItems) => [
...currentItems,
newItem
]);
Forgetting to Pass the Updated Value
If a child changes state in the parent but the parent does not pass the updated state back to the components that need it, those components will not receive the new value.
The complete flow should be maintained:
Child
↓
Callback
↓
Parent state update
↓
New state
↓
Props
↓
Children
Using Separate State for Derived Values
Avoid storing values separately when they can be calculated from existing state.
For example:
const total = items.length;
is often preferable to maintaining another total state variable.
Using Context Too Early
Context can solve certain sharing problems, but it is not required simply because multiple components need data.
For a small number of closely related components, props and lifted state are often easier to understand.
Best Practices
Identify Who Needs the Data
Before creating state, determine which components need the value.
Find the Closest Common Parent
If multiple components need the state, look for their nearest common parent.
Keep One Source of Truth
Store the shared value in one place rather than maintaining multiple synchronized copies.
Pass Values Down Through Props
Once the parent owns the state, pass the necessary values to children.
Pass Callbacks for Updates
If children need to request changes, pass callback functions.
Keep Unrelated State Local
Not all state needs to be shared.
Keeping local state local can make components easier to understand.
Derive Data When Possible
Calculate values from existing state instead of creating additional state variables unnecessarily.
Update State Immutably
Use patterns such as:
setItems((currentItems) => [
...currentItems,
newItem
]);
and:
setUser((currentUser) => ({
...currentUser,
name: "Karim"
}));
A Realistic Web4Learners Example
Imagine Web4Learners has an interactive React tutorial page with a lesson sidebar and lesson content.
The component structure could be:
CoursePage
/ \
↓ ↓
LessonSidebar LessonContent
The selected lesson needs to be shared between both components.
The state can be lifted into CoursePage:
import { useState } from "react";
function CoursePage() {
const [selectedLesson, setSelectedLesson] = useState(
"React Introduction"
);
const lessons = [
"React Introduction",
"Components",
"Props",
"State",
"Parent to Child",
"Child to Parent",
"Sibling Components",
"Sharing Data Between Components",
"Lifting State Up"
];
return (
<div>
<LessonSidebar
lessons={lessons}
selectedLesson={selectedLesson}
onSelectLesson={setSelectedLesson}
/>
<LessonContent
lesson={selectedLesson}
/>
</div>
);
}
The sidebar can request a lesson change:
function LessonSidebar({
lessons,
selectedLesson,
onSelectLesson
}) {
return (
<aside>
{lessons.map((lesson) => (
<button
key={lesson}
onClick={() => onSelectLesson(lesson)}
aria-current={
lesson === selectedLesson
? "page"
: undefined
}
>
{lesson}
</button>
))}
</aside>
);
}
The lesson content receives the selected lesson:
function LessonContent({ lesson }) {
return (
<main>
<h1>{lesson}</h1>
<p>
You are currently reading the {lesson} lesson.
</p>
</main>
);
}
The important part is where the state lives:
CoursePage
│
selectedLesson
/ \
↓ ↓
LessonSidebar LessonContent
│
│ callback
↓
setSelectedLesson()
The sidebar does not need to know how the lesson content works.
The content does not need to know how the sidebar works.
The parent coordinates both components through shared state.
This is exactly the kind of architecture that can be useful for an interactive learning platform such as Web4Learners.
Image Placement Instruction
Image placement: Add an educational diagram immediately after the section “The Basic Pattern”.
The image should visually explain the concept of lifting state up.
Show:
Parent
┌──────────────┐
│ Shared State │
│ value │
└──────┬───────┘
│
┌────────┴────────┐
↓ ↓
Child A Child B
updates value receives value
│ ↑
└── callback ─────┘
Use a modern Web4Learners visual style with rounded cards, subtle gradients, clean typography, clear arrows, and minimal text. The visual should make the idea understandable to a beginner without requiring the reader to study the code.
Second image placement: Add a visual diagram immediately after the section “One Source of Truth”.
Show a comparison:
❌ Separate State
Component A → value A
Component B → value B
Possible inconsistency
✓ Shared State
Parent
│
shared value
/ \
↓ ↓
Component A Component B
Use a modern, clean educational layout. Keep the image focused on the difference between duplicated state and a single shared source of truth.
Practice Exercise
Create a React application with this structure:
App
├── NameForm
└── NameDisplay
The App component should own the state:
const [name, setName] = useState("");
NameForm should contain an input field.
When the user types:
Karim
the parent state should be updated.
NameDisplay should receive the name through props and display:
Hello, Karim!
Challenge
Create a small product filtering application:
App
├── Filter
└── ProductList
The parent should own the selected category.
The Filter component should allow the user to choose:
- All
- Laptops
- Phones
- Accessories
The ProductList should display products matching the selected category.
The important requirement is that the category state must be stored in the common parent rather than separately inside Filter and ProductList.
Extra Challenge
Add a product count:
Showing 4 products
Calculate the count from the filtered product list instead of storing the count as separate state.
Cheat Sheet
| Concept | Pattern |
|---|---|
| Meaning | Move shared state to a common parent |
| When to use | Multiple components need the same state |
| State owner | Closest common parent |
| Send state down | Props |
| Request state update | Callback prop |
| Single source of truth | One shared state value |
| Child updates parent | onChange(value) |
| Parent updates state | setValue(value) |
| Updated value to child | <Child value={value} /> |
| Shared array | Store array in common parent |
| Add to array | setItems([...currentItems, item]) |
| Remove from array | setItems(currentItems.filter(...)) |
| Derived value | Calculate from existing state |
| Deep sharing | Consider Context |
| Avoid | Duplicate state and direct mutation |
Key Takeaways
- Lifting state up means moving state to the closest common parent of the components that need it.
- It is useful when multiple components need to access or coordinate the same state.
- The parent becomes the single source of truth.
- The parent passes state values to children through props.
- The parent can pass callback functions so children can request state changes.
- Lifting state up helps keep sibling components synchronized.
- You should not automatically move all state to the root component.
- Keep state as close as practical to the components that need it.
- Avoid maintaining multiple copies of the same state when they represent the same information.
- Derived values should often be calculated from existing state rather than stored separately.
- Update shared arrays and objects immutably.
- Props, callback functions, and lifted state work together to create predictable component communication.
- When shared data needs to reach many components across different levels, Context may be appropriate.
- Lifting state up is one of the core techniques for building well-organized React applications.