React - Lifting state up

  • Often, we need several different components to display the same data changes. We recommend hoisting state to their nearest parent component. Let's see how it performs.
  • In this section, we will create a temperature calculator that determines whether water can be boiled based on a given temperature.
  • We'll start with a component called BoilingVerdict . It receives the temperature in Celsius as a prop and then shows if it is enough to boil water:
function BoilingVerdict(props) {
    if (props.celsius >= 100) {
        return <p>The water would boil.</p>;
    }
    return <p>The water would not boil.</p>;
}
  • Next, let's create a component called Calculator . It renders an input component, you enter the temperature value, it will save the temperature in this.state.temperature .

  • Also, it renders the BoilingVerdict component with the current form values

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    return (
      <fieldset>
        <legend>Enter temperature in Celsius:</legend>
        <input
          value={temperature}
          onChange={this.handleChange} />
        <BoilingVerdict
          celsius={parseFloat(temperature)} />
      </fieldset>
    );
  }
}

add a second form

  • We have a letter requirement, in addition to the Celsius form, we provide a Fahrenheit form and keep them in sync.
  • We can extract a TemperatureInput component from the Calculatro component. We will add a scale property to it, which can be c or f.
const scaleNames = {
    c: 'Celsius',
    f: 'Fahrenheit'
}
class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}
  • We can now change the Calculator component to render two separate temperature sheets:
class Calculator extends React.Component {
    render() {
        return (
            <div>
                <TemperatureInput scale="c" />
                <TemperatureInput scale="f" />
            </div>
        );
    }
}
  • Now we have two forms, but when you fill in a value in one of the temperature forms, the other form does not update. This contradicts our needs: we want to keep them in sync.
  • We also cannot expose the BoilingVerdict component in the Calculator component. The Calculator component does not know the current temperature because the temperature is hidden inside the TemperatureInput component.

Write a conversion function

  • First, let's write two functions to convert between Celsius and Fahrenheit.
function toCelsius(fahrenheit) {
    return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheigt(celsius) {
    return (celsius * 9 / 5) + 32;
}
  • These two functions convert two temperatures. We'll write another function that takes the temperature string and a conversion function as arguments, and returns a string. We'll use it to calculate values ​​based on another form.
  • When temperature is invalid, it returns an empty string, and it holds three decimal places.
function tryConvert(temperaure, convert) {
    const input = parseFloat(temperature);
    if (Number.isNaN(input)) {
        return '';
    }
    const output = convert(input);
    const rounded = Math.round(output * 1000) / 1000;
    return rounded.toString();
}
  • For example, tryConvert('abc', toCelsius) returns an empty string, and tryConvert('10.22', toFahrenheit) returns '50.396'.

Ascension status

Now, the two TemperatureInput components each hold their own state:

class TemperatureInput extends React.Component {
    constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
        this.state = {temperature: ''};
    }
    handleChange(e) {
        this.setState({temperature: e.target.value});
    }
    render() {
        const temperature = this.state.temperature;
    }
}
  • However, we want to keep the two forms in sync. When we update the Celsius form, the Fahrenheit form should render the converted temperature and vice versa.
  • In React, sharing state is done by moving states to their nearest parent component. This is called "state machine boosting". We will move the current state from the TemperatureInput component to the Calculator component.
  • If the Calculator component has shared state, it becomes the "source of truth" for the current two temperature sheets. This time let them both have values ​​and be consistent. Since the static props of both TemperatureInput components come from the same parent component Calculator, the two forms will stay in sync.
  • Let's see how to do this step by step.
  • First, in the TemperatureInput component, we replace this.state.temperature with this.props.temperature . Although it will come from the Calculator component, for now let's assume this.props.temperature already exists:
render() {
    // Before: const temperature = this.state.temperature;
    const temperature = this.props.temperature;
}

We know that the props property is read-only. When temperature was previously in local state, the TemperatureInput component can call this.setState() to change it. However, now temperature is a prop from the parent and TemperatureInput has no control over it.
- In React, "controlled" components are usually used to solve this problem. Just like the input component accepts both the value and onChange properties, the custom TemperatureInput can also receive the temperature and onTemperatureChange properties from its parent component, the Claculator component.
- Now, when the TemperatureInput component wants to update its temperature, it calls this.props.onTemperatureChange :

handleChange(e) {
    // Before: this.setSate({temperature: e.target.value});
    this.props.onTemperatureChange(e.target.value);
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325360236&siteId=291194637