Lifting State Up
Share state between components by moving it to their closest common parent.
When two components need the same data, lift the state up to their nearest common parent. The parent holds the state and passes it down as props, along with callbacks to update it.
Why lift state
Keeping a single source of truth prevents two copies of the same value from drifting out of sync. Children become simpler — they just display props and report events upward.
Example
import { useState } from 'react';
function Child({ value, onChange }) {
return (
<input value={value} onChange={(e) => onChange(e.target.value)} />
);
}
function Parent() {
const [text, setText] = useState('');
return (
<div>
<Child value={text} onChange={setText} />
<Child value={text} onChange={setText} />
<p>Shared value: {text}</p>
</div>
);
}When to use it
- A temperature converter lifts the shared numeric value to the parent so both a Celsius and a Fahrenheit input always reflect the same measurement.
- A product filtering UI lifts the active filter criteria to the parent so the FilterBar and ProductGrid components both read from the same source of truth.
- A wizard form lifts each step's validated data to the top-level Wizard component so the final summary page can access every field without prop drilling through intermediaries.
More examples
Lift state to share between siblings
Moves shared state to the parent and passes it down with a callback so two sibling inputs stay in sync.
import { useState } from 'react';
function Parent() {
const [value, setValue] = useState('');
return (
<>
<InputA value={value} onChange={setValue} />
<InputB value={value} onChange={setValue} />
</>
);
}
function InputA({ value, onChange }) {
return <input value={value} onChange={e => onChange(e.target.value)} />;
}
function InputB({ value, onChange }) {
return <input value={value} onChange={e => onChange(e.target.value)} />;
}Filter bar lifting criteria
Lifts the search string into a parent so SearchBar updates it and ProductGrid derives filtered results from it.
import { useState } from 'react';
function ProductPage({ products }) {
const [search, setSearch] = useState('');
const filtered = products.filter(p =>
p.name.toLowerCase().includes(search.toLowerCase())
);
return (
<>
<SearchBar query={search} onSearch={setSearch} />
<ProductGrid items={filtered} />
</>
);
}Callback to update parent state
Passes a parent-owned updater through two levels of props so the deep QuantitySelector can modify cart state.
function QuantitySelector({ quantity, onQuantityChange }) {
return (
<div>
<button onClick={() => onQuantityChange(quantity - 1)}>-</button>
<span>{quantity}</span>
<button onClick={() => onQuantityChange(quantity + 1)}>+</button>
</div>
);
}
function CartItem({ item, updateQty }) {
return (
<div>
<span>{item.name}</span>
<QuantitySelector
quantity={item.qty}
onQuantityChange={qty => updateQty(item.id, qty)}
/>
</div>
);
}
Discussion