Sharing state between siblings
Sometimes two sibling components need to share the same data: for example, two fields that must stay synchronized, or a list and a total that depends on it. Where does that state live?
The technique is called "lifting state up": you place the state in the nearest common ancestor and pass down to the children whatever they need.
function Parent() {
const [text, setText] = useState("");
return (
<div>
<Input value={text} onChange={setText} />
<Echo text={text} />
</div>
);
}
function Input({ value, onChange }) {
return <input value={value} onChange={(e) => onChange(e.target.value)} />;
}
function Echo({ text }) {
return <p>You typed: {text}</p>;
}
The two key ideas:
- The state lives up top, in the common ancestor (
Parent). - The children are passed data (
value,text) and callbacks (onChange) through props. The child doesn't store the state: it reads it and notifies the parent when something should change.
This way the parent is the single source of truth, and all the children that depend on the data stay consistent. When the tree grows and passing callbacks through many levels becomes awkward, that's the signal to combine it with Context (and even with useReducer) to centralize both the state and its logic.