Get Started with Modern React: Step by Step
S02・V19: Lifting State Up (Part 1)
- We will set up two temperature inputs with different scales, in order to demo lifting state up in the next video.
Calculator Component
Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor.
Let’s see how this works in action. We will create a temperature calculator that calculates whether water would boil at a given temperature. The Calculator
component will keep track of the water’s temperature
in local state. It will get an initial value of an empty string.
import React, { useState } from "react";
import ReactDOM from "react-dom";
const Calculator = () => {
const [temperature, setTemperature] = useState("");
};
ReactDOM.render(
<Calculator />,
document.getElementById("root")
);
The Calculator
component will return a fieldset
element, within which we will place the temperature input. First, we will add a legend
. Next, we will add the input
element. We will make this input
element a controlled component, so we will set its value
as the temperature
. We will listen to updates to the input value using the onChange
attribute. Any changes typed into the input
field will update the temperature
state.
import React, { useState } from "react";
import ReactDOM from "react-dom";
const Calculator = () => {
const [temperature, setTemperature] = useState("");
return ( <fieldset> <legend>Enter temperature in Celsius:</legend> <input value={temperature} onChange={event => setTemperature(event.target.value)} /> </fieldset> );};
ReactDOM.render(
<Calculator />,
document.getElementById("root")
);
Under the input
element, we want to show if water would boil, given the temperature input. We will provide this with another component, which we will call BoilingVerdict
. It will have a prop called celcius
, which will take the temperature
string value converted into a floating point number. We will use JavaScript’s parseFloat()
function to do the conversion.
Now, we will define the BoilingVerdict
component. If the temperature equals or exceeds 100 degrees Celcius, then water would boil. Otherwise, the water would not boil.
import React, { useState } from "react";
import ReactDOM from "react-dom";
const BoilingVerdict = props => { if (props.celsius >= 100) { return <p>The water would boil.</p>; } return <p>The water would not boil.</p>;};
const Calculator = () => {
const [temperature, setTemperature] = useState("");
return (
<fieldset>
<legend>Enter temperature in Celsius:</legend>
<input
value={temperature}
onChange={event => setTemperature(event.target.value)}
/>
<BoilingVerdict celsius={parseFloat(temperature)} /> </fieldset>
);
};
ReactDOM.render(
<Calculator />,
document.getElementById("root")
);
Let’s test our Calculator
in the browser. Let’s type in 120
degrees Celcius. Our Calculator
shows that water would boil.
Adding a Second Input
We have a new requirement for our Calculator
app.
In addition to a Celcius input, we want to provide a Fahrenheit input, and they should be kept in sync.
We can start by extracting a TemperatureInput
component from Calculator
.
We will now move the body of Calculator
into TemperatureInput
.
The Calculator
component will now return the TemperatureInput
element.
import React, { useState } from "react";
import ReactDOM from "react-dom";
const BoilingVerdict = props => {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
};
const TemperatureInput = props => { const [temperature, setTemperature] = useState("");
return (
<fieldset>
<legend>Enter temperature in Celsius:</legend>
<input
value={temperature}
onChange={event => setTemperature(event.target.value)}
/>
<BoilingVerdict celsius={parseFloat(temperature)} />
</fieldset>
);
};
const Calculator = props => { return ( <div> <TemperatureInput /> </div> );};
ReactDOM.render(
<Calculator />,
document.getElementById("root")
);
Now, we will add a scaleNames
object. One key will be c
for “Celsius”. The other key will be f
for “Fahrenheit”. We can now pass in either key as a value to a new prop for TemperatureInput
. We will call this prop scale
, and pass in the value c
. We will have the Calculator
return a second TemperatureInput
element, with a scale
of f
. Now, in the TemperatureInput
component, we can update the legend
depending on the scale
prop value.
import React, { useState } from "react";
import ReactDOM from "react-dom";
const BoilingVerdict = props => {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
};
const scaleNames = { c: "Celsius", f: "Fahrenheit"};
const TemperatureInput = props => {
const [temperature, setTemperature] = useState("");
return (
<fieldset>
<legend>Enter temperature in {scaleNames[props.scale]}:</legend> <input
value={temperature}
onChange={event => setTemperature(event.target.value)}
/>
<BoilingVerdict celsius={parseFloat(temperature)} />
</fieldset>
);
};
const Calculator = props => {
return (
<div>
<TemperatureInput scale="c" /> <TemperatureInput scale="f" /> </div>
);
};
ReactDOM.render(
<Calculator />,
document.getElementById("root")
);
We have two inputs now, but when you enter the temperature in one of them, the other doesn’t update. This contradicts our requirement of keeping both of them in sync. In addition, we cannot display the BoilingVerdict
from Calculator
. The Calculator
does not know the current temperature because it is hidden inside the TemperatureInput
component.
We will fix these issues in the next video.
Summary
We set up two temperature inputs with different scales, in order to demo lifting state up in the next video.
Next Up…
In the next video, we will continue with our discussion of “lifting state up”, and get our two temperature inputs to sync up with each other.