Course: Section

Episode: Title

## S02・V20:Lifting State Up (Part 2)

Date Created: July 17th, 2019
Last Updated: 27 days ago

Objective
• We will lift state up in order to keep the temperature values in our two inputs in sync with each other.
Watch Video
Duration: 8m 44s

### Temperature Conversion Functions

We will write two functions to convert from Celsius to Fahrenheit and back. First, the conversion function from Fahrenheit to Celsius.

``````function toCelsius(fahrenheit) {
return ((fahrenheit - 32) * 5) / 9;
}``````

Second, the conversion function from Celsius to Fahrenheit.

``````function toFahrenheit(celsius) {
return (celsius * 9) / 5 + 32;
}``````

These two functions convert numbers.

We will write another function that takes a string temperature and a converter function as arguments and returns a string. We will use it to calculate the value of one input based on the other input. Inside the function, we will try to parse the `temperature` into a floating point number. If the result is invalid, that is `NaN`, we will return an empty string. Otherwise, we will run our converter function on the parsed `temperature`, and assign the result to a variable called `output`. Then, we will round the output to the third decimal place. Finally, we will return the string value of the rounded output.

``````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();
}``````

Currently, both rendered `TemperatureInput` components independently keep their `temperature` values in local state. However, we want these two inputs to be in sync with each other. When we update the Celsius input, the Fahrenheit input should reflect the converted temperature, and vice versa.

In React, sharing state is accomplished by moving it up to the closest common ancestor of the components that need it. This is called “lifting state up”.

Our Calculator app currently looks like this:

``````import React, { useState } from "react";
import ReactDOM from "react-dom";

function toCelsius(fahrenheit) {  return ((fahrenheit - 32) * 5) / 9;}
function toFahrenheit(celsius) {  return (celsius * 9) / 5 + 32;}
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();}
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 will remove the local state from the `TemperatureInput` and move it into the `Calculator` instead. If the `Calculator` owns the shared state, it becomes the “source of truth” for the current temperature in both inputs. It can instruct them both to have values that are consistent with each other. Since the props of both `TemperatureInput` components are coming from the same parent `Calculator` component, the two inputs will always be in sync.

Let’s see how this works step by step. First, we will move the state up from `TemperatureInput` to `Calculator`.

``````const TemperatureInput = props => {
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 => {
const [temperature, setTemperature] = useState("");
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
};``````

Now the `temperature` has been lifted into the `Calculator`, it can pass it into both rendered `TemperatureInput` elements via props. We will pass values to the `temperature` prop later. First, we will return to the `TemperatureInput` component, and ensure that the reference to `temperature` in the input is now via props.

``````const TemperatureInput = props => {
return (
<fieldset>
<legend>Enter temperature in {scaleNames[props.scale]}:</legend>
<input
value={props.temperature}        onChange={event => setTemperature(event.target.value)}
/>
<BoilingVerdict celsius={parseFloat(temperature)} />
</fieldset>
);
};``````

Now, we will move the `BoilingVerdict` up into the `Calculator` component. Updates to the `temperature` will also be handled by the `Calculator`. And, the handler will be passed down to the `TemperatureInput` component, via a prop that we will call `onTemperatureChange`. We will pass handler functions to the `onTemperatureChange` prop later. First, we should now use the `onTemperatureChange` prop within the `TemperatureInput` component.

``````const TemperatureInput = props => {
return (
<fieldset>
<legend>Enter temperature in {scaleNames[props.scale]}:</legend>
<input
value={props.temperature}
onChange={event => props.onTemperatureChange(event.target.value)}      />
</fieldset>
);
};

const Calculator = props => {
const [temperature, setTemperature] = useState("");

return (
<div>
<TemperatureInput
temperature={}        scale="c"
onTemperatureChange={}      />
<TemperatureInput
temperature={}        scale="f"
onTemperatureChange={}      />
<BoilingVerdict celsius={parseFloat(temperature)} />
</div>
);
};``````

Let’s return to the `Calculator` component. In addition to keeping a record of the `temperature` in state, the `Calculator` should also keep a record of which scale the temperature is referring to: Celsius or Fahrenheit.

We will add another state variable for the `scale`. We will set the initial scale value to Celsius.

``````const Calculator = props => {
const [temperature, setTemperature] = useState("");
const [scale, setScale] = useState("c");
return (
<div>
<TemperatureInput
temperature={}
scale="c"
onTemperatureChange={}
/>
<TemperatureInput
temperature={}
scale="f"
onTemperatureChange={}
/>
<BoilingVerdict celsius={parseFloat(temperature)} />
</div>
);
};``````

We will now calculate the temperature values in both Celsius and Fahrenheit for both inputs, depending on the temperate scale and temperature value.

First, to calculate the Celsius temperature, if the scale is Fahrenheit, then we will try to convert the input value into Celsius, otherwise, just use the value which was input.

In order to calculate the Fahrenheit temperature, if the scale is Celsius, then we will try to convert the input value into Fahrenheit, otherwise, just use the value which was input.

We can now pass these calcuated values into the rendered `TemperatureInput` elements.

``````const Calculator = props => {
const [temperature, setTemperature] = useState("");
const [scale, setScale] = useState("c");

const celsius =    scale === "f" ? tryConvert(temperature, toCelsius) : temperature;  const fahrenheit =    scale === "c" ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput
temperature={celsius}        scale="c"
onTemperatureChange={}
/>
<TemperatureInput
temperature={fahrenheit}        scale="f"
onTemperatureChange={}
/>
<BoilingVerdict celsius={parseFloat(temperature)} />
</div>
);
};``````

As a final step, we will need to define the handler functions which will be called when the input values change.

First, we will handle changes in the Celsius temperature input. We will call the handler `handleCelsiusChange`, which will take the `temperature` value as its input. We will set the scale to Celsius. And, we will set the temperature value.

Next, we will handle changes in the Fahrenheit temperature input. We will call the handler `handleFahrenheitChange`, which will take the `temperature` value as its input. We will set the scale to Fahrenheit. And, we will set the temperature value.

Now, we can pass these handlers into the rendered `TemperatureInput` elements.

``````const Calculator = props => {
const [temperature, setTemperature] = useState("");
const [scale, setScale] = useState("c");

const celsius =
scale === "f" ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit =
scale === "c" ? tryConvert(temperature, toFahrenheit) : temperature;

const handleCelsiusChange = temperature => {    setScale("c");    setTemperature(temperature);  };
const handleFahrenheitChange = temperature => {    setScale("f");    setTemperature(temperature);  };
return (
<div>
<TemperatureInput
temperature={celsius}
scale="c"
onTemperatureChange={handleCelsiusChange}      />
<TemperatureInput
temperature={fahrenheit}
scale="f"
onTemperatureChange={handleFahrenheitChange}      />
<BoilingVerdict celsius={parseFloat(temperature)} />
</div>
);
};``````

The entire app now looks like this:

``````import React, { useState } from "react";
import ReactDOM from "react-dom";

function toCelsius(fahrenheit) {
return ((fahrenheit - 32) * 5) / 9;
}

function toFahrenheit(celsius) {
return (celsius * 9) / 5 + 32;
}

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();
}

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 => {
return (
<fieldset>
<legend>Enter temperature in {scaleNames[props.scale]}:</legend>
<input
value={props.temperature}
onChange={event => props.onTemperatureChange(event.target.value)}
/>
</fieldset>
);
};

const Calculator = props => {
const [temperature, setTemperature] = useState("");
const [scale, setScale] = useState("c");

const celsius =
scale === "f" ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit =
scale === "c" ? tryConvert(temperature, toFahrenheit) : temperature;

const handleCelsiusChange = temperature => {
setScale("c");
setTemperature(temperature);
};

const handleFahrenheitChange = temperature => {
setScale("f");
setTemperature(temperature);
};

return (
<div>
<TemperatureInput
temperature={celsius}
scale="c"
onTemperatureChange={handleCelsiusChange}
/>
<TemperatureInput
temperature={fahrenheit}
scale="f"
onTemperatureChange={handleFahrenheitChange}
/>
<BoilingVerdict celsius={parseFloat(temperature)} />
</div>
);
};

ReactDOM.render(
<Calculator />,
document.getElementById("root")
);``````

Will now test the `Calculator` in the browser. Enter the value `100` into the Celcius input. Note that the value in the Fahrenheit input automatically shows as `212` degrees Fahrenheit, which is the boiling point of water.

Now, let’s enter the value `80` in the Fahrenheit input. Note that the value in the Celcius input automatically updates to be about `27` degrees Celcius.

Our inputs are now in sync.

### Summary

We lifted state up in order to keep the temperature values in our two inputs in sync with each other.

### Next Up…

In the next video, we will compare composition with inheritance.

Previous Video
Next Video