State and Events · Lesson 11 of 31
Lifting State Up in React
Sometimes two components need the same data: a filter box and the list it filters, or a cart icon and the product page. State lives in one component, so the answer is to move it up to their closest common parent and pass it down as props. This is called lifting state up, and it's the standard way React components share data.
In this lesson
- The problem: siblings with separate state
- Lifting state to the parent
- Controlled vs uncontrolled components
- Choosing where state should live
The problem
An FAQ where only one answer should be open at a time. If each panel keeps its own isOpen state, they can't know about each other:
import { useState } from "react";
function Panel({ title, children }) {
const [isOpen, setIsOpen] = useState(false); // each panel decides alone
return (
<section>
<h3>{title}</h3>
{isOpen ? <p>{children}</p> : <button onClick={() => setIsOpen(true)}>Show {title}</button>}
</section>
);
}
export default function App() {
return (
<>
<Panel title="Is it free?">Yes, every tutorial is free.</Panel>
<Panel title="Do I get a certificate?">Yes, after the final quiz.</Panel>
</>
);
}
Is it free? [Show Is it free?] Do I get a certificate? [Show Do I get a certificate?] › click “Show Is it free?” Is it free? Yes, every tutorial is free. Do I get a certificate? [Show Do I get a certificate?] › click “Show Do I get a certificate?” Is it free? Yes, every tutorial is free. Do I get a certificate? Yes, after the final quiz.
Both end up open. To make them coordinate, the parent has to own the decision.
Lift the state to the parent
- Remove the state from the children.
- Add it to the common parent.
- Pass the value and an event handler down as props.
import { useState } from "react";
function Panel({ title, isOpen, onShow, children }) { // no state: fully controlled by props
return (
<section>
<h3>{title}</h3>
{isOpen ? <p>{children}</p> : <button onClick={onShow}>Show {title}</button>}
</section>
);
}
export default function App() {
const [openIndex, setOpenIndex] = useState(0); // the single source of truth
return (
<>
<Panel title="Is it free?" isOpen={openIndex === 0} onShow={() => setOpenIndex(0)}>
Yes, every tutorial is free.
</Panel>
<Panel title="Do I get a certificate?" isOpen={openIndex === 1} onShow={() => setOpenIndex(1)}>
Yes, after the final quiz.
</Panel>
</>
);
}
Is it free? Yes, every tutorial is free. Do I get a certificate? [Show Do I get a certificate?] › click “Show Do I get a certificate?” Is it free? [Show Is it free?] Do I get a certificate? Yes, after the final quiz. › click “Show Is it free?” Is it free? Yes, every tutorial is free. Do I get a certificate? [Show Do I get a certificate?]
Data down, events up
- App owns openIndexthe single source of truth
- Props downeach Panel gets isOpen and onShow
- Event upa click calls onShow → setOpenIndex(1)
- Re-renderboth panels receive new isOpen values
A search box and a list
import { useState } from "react";
const courses = ["React.js", "Next.js", "TypeScript", "JavaScript", "Node.js"];
function SearchBar({ query, onQueryChange }) {
return <input placeholder="Filter courses" value={query} onChange={(e) => onQueryChange(e.target.value)} />;
}
function CourseList({ query }) {
const matches = courses.filter((c) => c.toLowerCase().includes(query.toLowerCase()));
return (
<>
<p>{matches.length} of {courses.length} courses</p>
<ul>{matches.map((c) => <li key={c}>{c}</li>)}</ul>
</>
);
}
export default function App() {
const [query, setQuery] = useState(""); // lifted: both children need it
return (
<>
<SearchBar query={query} onQueryChange={setQuery} />
<CourseList query={query} />
</>
);
}
[Filter courses…] 5 of 5 courses • React.js • Next.js • TypeScript • JavaScript • Node.js › type “js” into Filter courses [js] 3 of 5 courses • React.js • Next.js • Node.js
Controlled vs uncontrolled
The first Panel was uncontrolled: it owned its state and the parent couldn't influence it. The lifted Panel is controlled: its important values come from props. Controlled components are more flexible (the parent can coordinate them); uncontrolled ones are simpler to drop in. Many good components support both, like an <input> with either value or defaultValue.
Where should state live?
A quick checklist
- List every component that reads or changes the value.
- Find their closest common parent – the state goes there (or just above).
- If that means passing props through many layers that don't use them ("prop drilling"), consider composition with
childrenor Context – covered later in this course.
Keep state as low as possible, though. A text input's draft value that nobody else cares about should stay inside that input's component – lifting everything to the top makes the whole app re-render on every keystroke.
Practice
Try it yourself
Build a temperature converter: two inputs (Celsius and Fahrenheit) that always stay in sync.
Show solution
import { useState } from "react"; function TempInput({ label, value, onChange }) { return ( <label> {label} <input aria-label={label} value={value} onChange={(e) => onChange(e.target.value)} /> </label> ); } export default function App() { const [celsius, setCelsius] = useState("25"); // one source of truth, in Celsius const c = parseFloat(celsius); const fahrenheit = Number.isNaN(c) ? "" : String(Math.round((c * 9) / 5 + 32)); return ( <div> <TempInput label="Celsius" value={celsius} onChange={setCelsius} /> <TempInput label="Fahrenheit" value={fahrenheit} onChange={(f) => setCelsius(f === "" ? "" : String(Math.round(((parseFloat(f) - 32) * 5) / 9)))} /> </div> ); }Celsius [25] Fahrenheit [77] › clear Celsius and type “100” Celsius [100] Fahrenheit [212] › clear Fahrenheit and type “32” Celsius [0] Fahrenheit [32]
Only Celsius is stored; Fahrenheit is derived. Storing both would let them disagree.
Quick check
Where do you put state that two siblings need?
In their closest common parent, passed down as props.
How does a child change lifted state?
By calling a function prop the parent passes down.
What's a controlled component?
One whose important values are driven by props rather than its own state.
Summary
- To share state, lift it to the closest common parent and pass it down.
- Children report changes through function props: data down, events up.
- Keep one source of truth, derive the rest, and keep state as low as it can go.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.