About
To shared a state between two components, the most common operation is to move the variable up to their closest common ancestor. This is called “lifting state up”. (ie removing the local state from the descendant component and move it into its ancestor instead.)
Shared State: When we update an input, an other component should reflect the change (and vice versa)
The descendant components become “controlled”.
There should be a single “source of truth” for any data that changes in a React application. Usually, the state is first added to the component that needs it for rendering. Then, if other components also need it, you can lift it up to their closest common ancestor. Instead of trying to sync the state between different components, you should rely on the top-down data flow.
Lifting state involves writing more “boilerplate” code than two-way binding approaches, but as a benefit, it takes less work to find and isolate bugs. Since any state “lives” in some component and that component alone can change it, the surface area for bugs is greatly reduced. Additionally, you can implement any custom logic to reject or transform user input.
Articles Related
Core Modification
Temperature is the shared state.
handleChange(e) {
// Before: this.setState({temperature: e.target.value});
// After passing the function onTemperatureChange in the props constructor from the ancestor component
this.props.onTemperatureChange(e.target.value);
The ancestor component provides:
- the onTemperatureChange prop
- and the temperature prop (to replace the local temperature state).
The ancestor component will handle the change by modifying its own local state, thus re-rendering both inputs with the new values.
Derived state must not be in the state: Two state one storage
When two components are related: It is enough to store the value of the most recently changed input.
For instance:
- First State: temperature: '37', scale: 'celsius'
- Second State: temperature: '212', scale: 'fahrenheit'
We could have stored the value of both inputs but it turns out to be unnecessary.
If something can be derived from either props or state, it probably shouldn't be in the state.
Example
- Constant:
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
- Utility Function in order to convert Celsius to Fahrenheit:
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
- The ancestor component
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
}
handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature});
}
handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature});
}
render() {
const scale = this.state.scale;
const temperature = this.state.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={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}
- A descendant component
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onTemperatureChange(e.target.value);
}
render() {
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
- An other descendant components
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
- The rendering
ReactDOM.render(
<Calculator />,
document.getElementById('root')
);
<div id="root"></div>