React is a library for creating front end views. It has a big ecosystem of libraries that work with it. Also, we can use it to enhance existing apps.
In this article, we’ll look at the principle of lifting states up.
Lifting State Up
We should lift any shared state up to their closest common ancestor.
This way, one state can be shared between multiple child components by passing them down via props.
For example, if we want to build a calculator for converting lengths, we can write the following:
import React from "react";
import ReactDOM from "react-dom";
class LengthInput extends React.Component {
constructor(props) {
super(props);
const { length } = this.props;
this.state = { length };
}
handleChange(e) {
this.props.onChange(e);
}
componentWillReceiveProps(props) {
const { length } = props;
this.setState({ length });
}
render() {
const { unit } = this.props;
return (
<>
<label>Length ({unit})</label>
<input
value={this.state.length}
onChange={this.handleChange.bind(this)}
placeholder={this.props.unit}
/>
<br />
</>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { lengthInMeter: 0, lengthInFeet: 0 };
}
handleMeterChange(e) {
this.setState({ lengthInMeter: +e.target.value });
this.setState({ lengthInFeet: +e.target.value \* 3.28 });
}
handleFeetChange(e) {
this.setState({ lengthInFeet: +e.target.value });
this.setState({ lengthInMeter: +e.target.value / 3.28 });
}
render() {
return (
<div className="App">
<LengthInput
length={this.state.lengthInMeter}
onChange={this.handleMeterChange.bind(this)}
unit="meter"
/>
<LengthInput
length={this.state.lengthInFeet}
onChange={this.handleFeetChange.bind(this)}
unit="feet"
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
In the code above, we have a length converter than converts meters to feet when we’re typing in the meters field and vice versa.
What we’re doing is that we keep the lengths all in the App
component. This is why the principle is called lifting the states up.
Since we’re keeping the lengths in the App
component, we have to pass them down to the LengthInput
components.
To do that, we pass props to them. Also, we pass in the units, and the change handler functions down to our LengthInput
components, so that they can the functions to update the states in the App
component.
In the handleFeetChange
and handleMeterChange
functions, we set the state according to the values entered in the LengthInput
components.
We call this.setState
in both functions to set the states. Each time setState
is called, render
will be called, so that the latest state will be passed down to our LengthInput
components.
In the LengthInput
components, we have the componentWillReceiveProps
hook which will get the latest prop values and then set the length
state accordingly.
this.state.length
is set as the value of the input
elements in LengthInput
, so the inputted value will be shown.
The advantage of lifting the states up to the parent component is that we only have to keep one copy of the state. Also, we don’t have to repeat the processing of the states in different child components.
The values of the inputs stay in sync because they’re computed from the same state.
Conclusion
When writing React apps, it’s recommended that we move shared states between child components into their parent component. This is called lifting the states up.
We want to do this so that we don’t have to duplicate the processing of data in child components, and we only have a single source of truth.
To do this, we can pass in data and function as props down to child components. This way, functions from the parent component can be called from child components.
Then to update the states of child component with the latest values passed from the props, we can use the componentWillReceiveProps
hook to update the states from the props.