React Sharing State Between Components
Table of Contents + −
In the last lesson, React Lifting State Up, you moved state to a shared parent so two children could agree on one value. This lesson zooms out to the bigger idea: letting several components read and update the same value instead of each keeping a private copy that drifts apart.
🤔 Why share state at all?
Picture an online shop. The header at the top shows a cart count like “Cart (3)”. A product card lower down has an “Add to cart” button. These are two different components, but they care about the same number.
- If the header keeps its own count and the product card keeps its own count, they are two separate numbers.
- You click “Add to cart”. The product card’s number goes up, but the header still shows the old value. Now the page lies to the user.
- The fix is to stop having two copies. There should be just one count, and both components point at it.
That single shared value is what we mean by shared state. One value, many components looking at it.
🔑 One source of truth
The rule that makes sharing work is simple: the value lives in one place, and everyone else reads it from there.
- Keep the real value in ONE component. That place is the single source of truth.
- Every other component does not store its own copy. It receives the value through props.
- When the value changes, it changes in that one place. So every component reading it sees the new value right away.
Nobody owns a private copy. There is one number, and the header and product card both look at it.
No copies, just one original
Think of one value written on a whiteboard that the whole room can see. If everyone copied it into their own notebook instead, the copies would slowly go out of date. Shared state keeps the value on the whiteboard, not in private notebooks.
📍 Where does the shared value live?
If two components must share a value, you put it where both can reach it. In a React tree, that place is their closest common parent.
- Find the components that need the value. In our shop, that is the header and the product card.
- Walk up the tree to the first parent that has both of them inside it.
- Put the state in that parent. The parent then passes it down to each child as a prop.
This little diagram shows the shape. The parent holds the count and hands it to both children.
So the parent is the one source of truth. The header reads the value, and the product card asks to change it.
🔁 Reading AND updating the same value
Here is the part people miss. A child can do more than just read shared state, it can also update it. The trick is that the parent passes down both the value and a function to change it.
- The parent owns the state, so the parent owns the setter too.
- It passes the value down for children that need to READ it.
- It passes a function down for children that need to UPDATE it.
- A child calls that function, the parent’s state changes, and then every child reading the value re-renders with the new one.
See why the header updates the instant the button is clicked? The product card never touches the header. It just calls the function, the parent’s count goes up, and the header reads the new count automatically. That loop is the heart of component communication.
🛒 A full worked example
Let’s build the shop for real. The parent Shop holds count. The Header reads it. The Product updates it. Read the code first, then we’ll walk through it.
import { useState } from "react";
// PARENT - owns the shared statefunction Shop() { const [count, setCount] = useState(0);
const addToCart = () => setCount((prev) => prev + 1);
return ( <div> <Header count={count} /> <Product addToCart={addToCart} /> </div> );}
// CHILD 1 - only READS the shared valuefunction Header({ count }) { return <h1>Cart ({count})</h1>;}
// CHILD 2 - only UPDATES the shared valuefunction Product({ addToCart }) { return ( <div> <p>Wireless Headphones</p> <button onClick={addToCart}>Add to cart</button> </div> );}Now the walkthrough, step by step:
ShopcallsuseState(0), so the one true count starts at0and lives inShop.Shoppassescountdown toHeader, so the header can show it.Shoppasses theaddToCartfunction down toProduct, so the product can change the count.- A click on the button runs
addToCart, which callssetCount. The count inShopgoes from0to1. Shopre-renders.Headergets the newcount={1}and shows “Cart (1)”. You never touched the header directly.
When the page first loads and you click “Add to cart” once, the header updates on its own.
Output
Before click: Cart (0)After 1 click: Cart (1)After 3 clicks: Cart (3)Value down, function down
Pass the value down to whoever READS it. Pass the setter (or a function that calls the setter) down to whoever UPDATES it. The child never owns the value. It only borrows the value and the way to change it.
🔍 The same idea with a search filter
Sharing state is not only for counts. Any value two components must agree on works the same way. A common one is a search filter shared between a search box and a results list.
- The parent holds the
querytext. That is the single source of truth. - The search box reads
queryand calls a setter when the user types. - The results list reads
querytoo, and filters what it shows.
Here both children share one query. The box updates it, the list reads it.
import { useState } from "react";
function ProductFinder() { const [query, setQuery] = useState(""); const items = ["Phone", "Laptop", "Headphones", "Camera"];
const results = items.filter((item) => item.toLowerCase().includes(query.toLowerCase()) );
return ( <div> <SearchBox query={query} setQuery={setQuery} /> <ResultsList results={results} /> </div> );}
function SearchBox({ query, setQuery }) { return ( <input value={query} placeholder="Search products" onChange={(e) => setQuery(e.target.value)} /> );}
function ResultsList({ results }) { return ( <ul> {results.map((item) => ( <li key={item}>{item}</li> ))} </ul> );}So when you type “la” in the box, setQuery updates the parent. The parent re-runs the filter and passes the smaller results to the list. The list shows just “Laptop”. One shared value drives two components at once.
⚠️ When sharing through props gets painful
Sharing through a common parent is the right move for nearby components. But it has a limit you should know about before you hit it.
- It works great when the parent and the children are close together in the tree.
- When the value has to travel through many layers, each layer must accept the prop and pass it down again, even layers that don’t use it.
- This passing of props through many middle components just to reach a deep child is called prop drilling. It gets tiring and clutters every component in the path.
The next lesson is all about prop drilling. Later, a tool called Context will let very far apart components share a value without passing props through every layer in between.
Right tool for the distance
For components that sit close together, lifting state to a shared parent is clean and easy. Reach for heavier tools like Context only when the value must travel far and prop drilling starts to hurt. Don’t jump to the big tool too early.
🧩 What You’ve Learned
- ✅ Shared state means one value that several components read and update, not a private copy in each
- ✅ The value lives in one place, the single source of truth, and others receive it through props
- ✅ Put shared state in the closest common parent of the components that need it
- ✅ The parent passes the value down for reading and a function down for updating
- ✅ When a child calls that function, the parent’s state changes and every reader re-renders with the new value
- ✅ Lifting state to a shared parent fits nearby components, but deep sharing leads to prop drilling and later Context
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What does 'single source of truth' mean when sharing state?
Why: Shared state keeps the value in exactly one place. Other components read it through props instead of holding their own copies, so the value can never disagree with itself.
- 2
Where should you put state that two sibling components need to share?
Why: Put shared state in the closest common parent of the components that need it. The parent then passes the value (and a setter) down to each child.
- 3
How can a child component update a value that the parent owns?
Why: The parent owns the state and the setter, so it passes a function down. The child calls that function, the parent's state changes, and every component reading the value re-renders.
- 4
What problem appears when shared state must travel through many layers of components?
Why: Passing a prop through many middle components just to reach a deep child is called prop drilling. It clutters every component in the path, which is why Context exists for far-apart sharing.
🚀 What’s Next?
You can now share one value across several components by keeping it in a common parent and passing it down. The thing is, when that parent is far above the child, every component in between has to forward the prop. That’s the pain we look at next, and where it starts to push you toward a better tool.