React course menu
Lifting State Up in React: Sharing State Between Components
Share state between React components by lifting it up to their closest common parent: passing state down as props and setter functions as event props, a single source of truth, deciding where state lives, and when to reach for context.
On this page
Each component's state is private. When two components need the same data (for example a search box and the list it filters), move that state to their closest common parent and pass it down. This is called lifting state up.
The problem#
Two accordion panels where only one should be open at a time. If each panel keeps its own isOpen state, they cannot know about each other:
import { useState } from "react";
function Panel({ title, children }) {
const [isOpen, setIsOpen] = useState(false);
return (
<section style={{ border: "1px solid #ddd", padding: 8, marginBottom: 6 }}>
<strong>{title}</strong>{" "}
<button onClick={() => setIsOpen(!isOpen)}>{isOpen ? "Hide" : "Show"}</button>
{isOpen && <p>{children}</p>}
</section>
);
}
export default function App() {
return (
<div>
<Panel title="Delivery">Orders arrive in 3–5 days.</Panel>
<Panel title="Returns">Return within 10 days of delivery.</Panel>
</div>
);
}Both can be open at once.
The solution: lift the state#
- Remove the state from the children.
- Add it to the common parent, and pass it down as props.
- Pass down functions so children can ask the parent to change it.
import { useState } from "react";
function Panel({ title, isOpen, onShow, children }) {
return (
<section style={{ border: "1px solid #ddd", padding: 8, marginBottom: 6 }}>
<strong>{title}</strong>{" "}
{isOpen ? <p>{children}</p> : <button onClick={onShow}>Show</button>}
</section>
);
}
export default function App() {
const [openIndex, setOpenIndex] = useState(0);
return (
<div>
<Panel title="Delivery" isOpen={openIndex === 0} onShow={() => setOpenIndex(0)}>
Orders arrive in 3–5 days.
</Panel>
<Panel title="Returns" isOpen={openIndex === 1} onShow={() => setOpenIndex(1)}>
Return within 10 days of delivery.
</Panel>
</div>
);
}Now the parent decides which panel is open, so opening one closes the other. The Panel components are controlled by their parent through props.
Search box and list#
The same pattern for a filter: the search text is needed by both the input and the results, so it lives in their parent.
import { useState } from "react";
const RECIPES = ["Paneer butter masala", "Masala dosa", "Veg biryani", "Dal makhani", "Paneer tikka"];
function SearchBar({ query, onQueryChange }) {
return <input value={query} onChange={(e) => onQueryChange(e.target.value)} placeholder="Search recipes" />;
}
function RecipeList({ recipes }) {
if (recipes.length === 0) return <p>No recipes found.</p>;
return <ul>{recipes.map((r) => <li key={r}>{r}</li>)}</ul>;
}
export default function App() {
const [query, setQuery] = useState("");
const visible = RECIPES.filter((r) => r.toLowerCase().includes(query.toLowerCase()));
return (
<div>
<SearchBar query={query} onQueryChange={setQuery} />
<p>{visible.length} recipes</p>
<RecipeList recipes={visible} />
</div>
);
}Data flows down (query, recipes) and events flow up (onQueryChange). This one-way data flow makes React apps predictable: to find out why something shows a value, follow the props up to the state.
A single source of truth#
Every piece of state should live in exactly one component, the one that "owns" it. Avoid copying props into state (useState(props.value)): the copy does not update when the prop changes, and then two components disagree.
Where should state live?#
For each piece of state:
- Find every component that renders something based on it.
- Find their closest common parent.
- Put the state there (or higher, if that makes more sense).
If you end up passing props through many layers that do not use them ("prop drilling"), consider context.
Common mistakes#
Practice#
Practice exercise
Build a temperature converter with two inputs, Celsius and Fahrenheit. Typing in either updates the other. Keep a single piece of state in the parent (the temperature in Celsius) and calculate Fahrenheit from it.
Show hint
Store celsius as a string in state. Fahrenheit is celsius * 9 / 5 + 32. When the Fahrenheit input changes, convert back with (f - 32) * 5 / 9. Round displayed values to one decimal.
Show solution
import { useState } from "react";
function TemperatureInput({ label, value, onChange }) {
return (
<label style={{ display: "block", marginBottom: 8 }}>
{label}: <input type="number" value={value} onChange={(e) => onChange(e.target.value)} />
</label>
);
}
const round1 = (n) => String(Math.round(n * 10) / 10);
export default function App() {
const [celsius, setCelsius] = useState("25");
const fahrenheit = celsius === "" ? "" : round1((Number(celsius) * 9) / 5 + 32);
return (
<div>
<TemperatureInput label="Celsius" value={celsius} onChange={setCelsius} />
<TemperatureInput
label="Fahrenheit"
value={fahrenheit}
onChange={(f) => setCelsius(f === "" ? "" : round1(((Number(f) - 32) * 5) / 9))}
/>
</div>
);
}Because Celsius is stored rounded to one decimal, some Fahrenheit values change slightly as you type them: 100 °F becomes 37.8 °C and shows 100 again, but 2 °F becomes −16.7 °C, which converts back to 1.9 °F. Storing which field was edited plus its exact text avoids this, a common refinement.
Key takeaway#
Next: Effects with useEffect.
Finished this lesson?