What is lifting state up?
Solution and explanation
What it is
When two or more components need the same changing data, you move ("lift") that state to their **closest common parent**. The parent holds the state and passes the value and a setter (or callback) down as props. There is then a single source of truth.
Example: filter panel and result list
function JobSearchPage() {
const [filters, setFilters] = useState({ city: '', remote: false });
return (
<div className="layout">
<FilterPanel filters={filters} onChange={setFilters} />
<JobResults filters={filters} />
</div>
);
}
function FilterPanel({ filters, onChange }) {
return (
<label>
<input type="checkbox" checked={filters.remote}
onChange={e => onChange({ ...filters, remote: e.target.checked })} />
Remote only
</label>
);
}
Before lifting, FilterPanel and JobResults each had their own copy and drifted out of sync. After lifting, both read the same object.
How to decide where state lives
- List every component that reads or changes the value.
- Find their nearest common ancestor.
- Put the state there (or higher only if needed).
When lifting is not enough
If the common parent is many levels up and intermediate components only pass props along (**prop drilling**), consider:
- **Composition:** pass components as
childrenso the middle layers do not need the data. - **Context:** for app-wide values (current user, theme, language).
- **A store** (Zustand, Redux Toolkit) for large, frequently changing shared state.
- **URL state:** filters and page numbers belong in the query string (
?city=Pune&page=2) so they survive refresh and can be shared.
Summary
Lift shared state to the closest common parent and pass it down; this keeps one source of truth and predictable one-way data flow.
