State and Life Cycle

State and Life Cycle

Consider the tick clock example in the previous section (Chapter 3).
So far, we have only learned a way to update the UI.
We call ReactDOM.render() to change the rendering output:

import React from 'react';
import ReactDOM from 'react-dom';

function tick() {
    const element = (
        <div>
            <h1>Hell world</h1>
            <h2>It is {new Date().toLocaleTimeString()}</h2>
        </div>
    );
    ReactDOM.render(
        element,
        document.getElementById('root')
    );
}

setInterval(tick, 1000);

In this section, we will learn how to make Clock components truly reusable and encapsulated. It will set its own timer and update it every second.
We can start with the appearance of the encapsulated clock:

import React from 'react';
import ReactDOM from 'react-dom';

function Clock(props) {
    return (
        <div>
            <h1>hello world</h1>
            <h2>It is {props.date.toLocaleTimeString()}</h2>
        </div>
    );
}

function tick() {
   ReactDOM.render(
       <Clock date={new Date()} />,
       document.getElementById('root')
   );
}

setInterval(tick, 1000);

However, it lacks a key requirement: the fact that the clock sets a timer and updates the UI every second should be the implementation details of the clock. Ideally, we would write this time and update the time by the clock itself:

ReactDOM.render(
    <Clock />,
    document.getElementById('root')
);

To achieve this, we need to add "state" to the clock component.

state is similar to props, but it is private and entirely controlled by components.

As we mentioned earlier, classes defined as components have some additional functionality. Internal state is a function that can only be used by class components.

Change Functional Form Components to Class Form Components

You can convert functional components (such as Clock) into class components in five steps:

  1. Create an ES6 class with the same name as the extended React.Component.

  2. Add a single empty method render().

  3. Move the body of the function to the render() method.

  4. Replace props with this.props in the render() body.

  5. Delete the remaining empty function declarations.

class Clock extends React.Component {
   render() {
       return (
           <div>
               <h1>hello world</h1>
               <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
           </div>
       )
   };
}

Clock is now redefined as a class component rather than a functional component.
This allows us to use additional functions such as internal state and life cycle hooks.

Adding state to class components

We will move date from props to state in three steps:

1) Replace this.props.date with this.state.date in render():
class Clock extends React.Component {
    render() {
        return (
            <div>
                <h1>hello world</h1>
                <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
            </div>
        );
    }
}
2) Add a class constructor that assigns the initial this.state:
class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
    }
    
    render() {
        return (
            <div>
                <h1>hello world</h1>
                <h2>It is {this.state.date.toLocalTimeString()}.</h2>
            </div>
        );
    }
}

Notice how we pass props to the constructor of the base class:

constructor(props) {
    super(props);
    this.state = {date: new Date()};
}

Class components should always call base class constructors with props.

3) Delete date prop from the <Clock/> element:
ReactDOM.render(
    <Clock />,
    document.getElementById('root')
);

We will add the timer code back to the component itself later. The results are as follows:

import React from 'react';
import ReactDOM from 'react-dom';

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
    }
    
    render() {
       return (
           <div>
               <h1>hello world</h1>
               <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
           </div>
       );
    }
}

ReactDOM.render(
    <Clock />,
    document.getElementById('root')
);

Next, we will have the clock set its own timer and update it every second.

Adding declarative periodic methods to classes

In applications with many components, it is important to release the resources that components occupy when they are destroyed.
We want to set up a timer when we first render the clock to DOM. This is called "mounting" in React.
We also want to clear the timer when the DOM generated by the clock is deleted. This is called "unmounting" in React.
We can declare special methods on component classes to run some code when components are loaded and unloaded:

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
    }
    
    componentDidMount() {
        // Components have been installed
    }
    
    componentWillUnmount() {
        // Components will be uninstalled
    }
    
    render() {
       return (
           <div>
               <h1>hello world</h1>
               <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
           </div>
       );
    }
}

These methods are called "life cycle hooks".
The component DidMount () subunit runs after the component output is rendered to the DOM. This is a good place to set up a timer:

componentDidMount() {
    this.timerID = setInterval(
        () => this.tick(),
        1000
    )
}

Notice how we save the timer ID right here.
Although this.props is set by React itself, and this.state has a special meaning, if you need to store something that is not used for visual output, you can manually add additional fields to the class.
If you don't use render(), it shouldn't be placed in state.
We will remove the timer in the component WillUnmount () life cycle hook:

componentWillUnmount() {
    clearInterval(this.timerID);
}

Finally, we will implement the tick() method that runs every second.
It will use this.setState() to schedule updates to the component's local state:

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
    }
    
    componentDidMount() {
        this.timerID = setInterval(
            () => this.tick(),
            1000
        )
    }
    
    componentWillUnmount() {
        clearInterval(this.timerID);
    }

    tick() {
        this.setState({
            date: new Date()
        });
    }
    
    render() {
       return (
           <div>
               <h1>hello world</h1>
               <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
           </div>
       );
    }
}
ReactDOM.render(
    <Clock />,
    document.getElementById('root')
);

Now the clock ticks every second. That's great...

Let's quickly review what happened and the order in which methods were invoked:

  • 1) When < Clock /> is passed to ReactDOM.render(), React calls the constructor of the Clock component. Because Clock needs to display the current time, it initializes this.state with objects that include the current time. We will update this state later.

  • 2) React then calls the render() method of the Clock component. This is how React learns what to display on the screen. React then updates the DOM to match the rendering output of the clock.

  • 3) When the clock output is inserted into the DOM, React calls the componentDidMount() life cycle hook. In it, the clock component requires the browser to set a timer and call tick() once a second.

  • 4) The browser calls tick() method every second. In it, the Clock component schedules UI updates by calling setState() and objects containing the current time. Because of the setState() call, React knows that the state has changed and calls render() again to see what should be displayed on the screen. At this point, the render() method's this.state.date will be different, so the render output will include the update time. React updates DOM accordingly.

  • 5) If the clock component is removed from the DOM, React calls the component WillUnmount () lifecycle hook, so the timer stops.

Use state correctly

There are three things you should know about setState():

Do not modify state directly

For example, this will not re-render the component:

// This is wrong.
this.state.comment = 'hello';

setState() should be used instead of:

// That's right.
this.setState({comment: 'hello'});

The only place where this.state can be allocated is the constructor.

state updates may be asynchronous

React can batch multiple setState() into a single update to achieve higher performance.
Because this.props and this.state may be updated asynchronously, you should not rely on their values to calculate the next state.
For example, this code may not be able to update the counter:

// This is wrong.
this.setState({
    counter: this.state.counter + this.props.increment,
});

To solve this problem, you should call setState() with a callback function instead of an object. The callback function takes the previous state as the first parameter and the props when the update is applied as the second parameter:

// That's right.
this.setState((prevState, props) => ({
    counter: prevState.counter + props.increment
}));

We use the arrow function above, but it can also be used with regular functions:

// That's also true. Change the clipping function to a normal function.
this.setState(function(prevState, props) {
   return {
       counter: prevState.counter + prps.increment
   }
});
state updates are merged

When setState() is called, React merges the objects you provide into the current state.
For example, your state may contain several independent variables:

constructor(props) {
    super(props);
    this.state = {
        posts: [],
        comments: []
    }
}

Then, you can use a separate setState() to update them independently:

componentDidMount() {
    fetchPosts().then(response => {
        this.setState({
            posts: response.posts
        });
    });
    
    fetchComments().then(response => {
        this.setState({
            comments: response.comments
        }});
    });
}

The merge is shallow, so this.setState({comments}) does not affect this.state.posts. It just completely replaced this.state.comments.

Data flows downward.

Neither parent component nor child component knows whether a component has a State or no State, and they should not care whether it is a functional component or a class component.

That's why State is usually set as a local variable or encapsulated inside a component. No component can access it except the one that owns and sets it.

Components can choose to pass their state as props to their subcomponents:

<h2>Is is {this.state.date.toLocaleTimeString()}.</h2>

This also applies to user-defined components:

<formattedDate date={this.state.data} />

The FormattedDate component receives date in its props and does not know whether it is from the clock state, props or manual input:

function FormattedData(props) {
    return <h2>Is is {props.date.toLocaleTimeString()}.</h2>;
}

This is often referred to as a "top-down" or "one-way" data flow. Any state is always owned by some specific component, and any data or UI derived from that state can only affect the "below" component in the tree.

If you imagine a component tree as a waterfall flow of props, the state of each component is like an additional source of water, which can connect it at any point, but also downstream.

To show that all components are truly isolated, we can create an App component to render three <Clock>:

function App() {
    return (
        <div>
            <Clock />
            <Clock />
            <Clock />
        </div>
    );
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
);

Each clock sets its own timer and updates it independently. In React applications, whether a component is stateless or stateless is considered the implementation details of components that may change over time. You can use stateless components within stateful components, and vice versa.

Keywords: React

Added by quanghoc on Tue, 18 Jun 2019 03:59:15 +0300