Lifting State Up
Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor. In React, sharing state is accomplished by moving it up to the parent component rather than trying to sync state between child components.
The Problem: Sibling Communication
Imagine a scenario where you have two components that need to stay in sync. For example, a temperature calculator that has two input fields: one for Celsius and one for Fahrenheit.
When you type in the Celsius input, the Fahrenheit input should update automatically, and vice-versa.
If both inputs maintain their own local state independently, they will fall out of sync.
The Solution: A Single Source of Truth
To fix this, we remove the local state from the individual input components and move it into their closest common ancestor.
1. Remove Local State from Children
Instead of keeping the state inside the child, we pass the current value and a change handler down as props.
// Before
function TemperatureInput() {
const [temperature, setTemperature] = useState('');
// ...
}
// After
function TemperatureInput({ temperature, onTemperatureChange }) {
// ...
return (
<input
value={temperature}
onChange={(e) => onTemperatureChange(e.target.value)}
/>
);
}
2. Add State to the Common Ancestor
Now, we introduce the state to the parent component that renders both inputs. This parent will hold the "source of truth".
function Calculator() {
const [temperature, setTemperature] = useState('');
const [scale, setScale] = useState('c');
const handleCelsiusChange = (temperature) => {
setScale('c');
setTemperature(temperature);
};
const handleFahrenheitChange = (temperature) => {
setScale('f');
setTemperature(temperature);
};
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={handleFahrenheitChange} />
</div>
);
}
Unidirectional Data Flow
This pattern naturally reinforces React's unidirectional (top-down) data flow:
- State lives in the parent.
- The parent passes down the state value as a prop to children.
- The parent passes down a callback function as a prop to children.
- When a child detects a change (e.g., a user typing), it calls the callback function.
- The callback function in the parent updates the state.
- The parent re-renders, passing the new state down to all children.
When to Lift State Up
You should lift state up when:
- Two or more components need to share the same state.
- Components need to communicate or affect each other based on user interactions.
- You need a single source of truth to avoid bugs caused by out-of-sync duplicate states.
If a piece of state is only used by a single component and doesn't affect anything else, it's perfectly fine to keep it local to that component.