The React Workshop
上QQ阅读APP看书,第一时间看更新

The Mount Lifecycle

The mount lifecycle is called twice: before and immediately after React renders the component into DOM. Note that mounting alone happens only once; there is no scope in a React world where you can remount a React component into the DOM. When we say that React renders to the DOM, it means that this is when React processes the JSX, converts it to HTML, and shows it on the browser.

Mounting is where a lot of the functionality will take place specific to initializing a component's state at the time of loading. Mounting happens when your app loads up for the first time, when you navigate to a particular component using something like React router, or it could be something like when you add a component to a page dynamically, like through conditional renders or loops. The first one of these functionalities is one you should be very comfortable with at this point: the constructor.

constructor()

We have used the contructor(props) method in previous exercises throughout this book to initialize the overall state of our React component and to set up the call to the parent constructor with the props arguments that were passed in. Previously, we have used the constructor, but never with a focus on what was actually happening as part of the lifecycle.

The call to the constructor happens almost immediately before the component is first rendered to a page. Essentially, when our code is loaded, JavaScript will look for the first React component that gets rendered (the root component). Then, each child component below the root is rendered. React starts off each component with the constructor statement which initializes the state and the props for each component along the way and tells React precisely how it needs to handle rendering each of those components.

Exercise 4.02: Conditional Rendering and the Mount Lifecycle

In this exercise, we'll demonstrate how the mount lifecycle in our components is affected through the conditional rendering techniques we learned in the previous chapter. Let's see how to do that:

  1. Start off by creating a new React project; call it conditional.
  2. Start the project and take a look at the browser window that should have opened for you automatically:

    $ npx create-react-app conditional

    $ cd conditional

    $ yarn start

  3. Delete src/logo.svg and delete the contents of src/App.css. Strip out the import statements and code we don't need.  
  4. Clean out the contents of the App component and replace it with a class component instead. Since we are using a class component here, we will need to change the import statement at the top to also import {Component} from react:

    import React, { Component } from 'react';

    import "./App.css";

        class App extends Component {

        render() {

        return (

                  <p className="App">Hello Conditional</p>

              );

            }

          }

    export default App;

    By running the app (via yarn start) and opening up our browser to http://localhost:3000, we will see the initial component, like the following screenshot:

    Figure 4.6: Initial component

  5. Add a new class component to a new file, src/LifecycleTest.js, called LifecycleTest. We'll need the standard React import statements and an export default statement as well.

    The component will just have a simple constructor that outputs a log statement and a simple return statement that just returns any text wrapped in a paragraph tag:

    import React, { Component } from 'react';

    class LifecycleTest extends Component {

      constructor(props) {

      super(props);

      console.log('LifecycleTest Constructor');

    }

    render() {

      return <p>I only show up if the conditional is true!</p>;

      }

     }

    export default LifecycleTest;

  6. Add the LifecycleTest import to src/App.js:

    import LifecycleTest from "./LifecycleTest";

  7. Change our App component to be able to conditionally render this new component of ours. Add a single line to our return statement using a boolean value, the and operator, and a reference to the LifecycleTest JSX component:

      render() {

        return (

          <p className="App">

            Hello Conditional

              {true && <LifecycleTest />}

          </p>

      );

    }

    When we take a look at the JavaScript console, we should see the console.log statement from the constructor of the LifecycleTest call:

    Figure 4.7: App showing the conditional message

  8. Change the boolean statement to false instead, and take a look at the log statement; now you should not see the constructor's console.log statement:

    The output is as follows:

Figure 4.8: Conditional statement not shown

With that, you can see how the mount lifecycle method is affected by conditional rendering. It's important to see how conditional rendering can affect lifecycle methods, since you may need to rely on knowing when components are getting mounted and rendered into your DOM.

render()

The render function is another lifecycle method that we should be very comfortable with at this point. render is the point where React converts the JSX that represents our component to the DOM. But up until now, we have not really used it much for anything outside of rendering JSX with a return statement. The reality is that render() is just a normal function and can actually have a good amount of other logic inside of it. For example, you can set locally scoped variables for use in your final JSX that gets rendered. There is a lot that you can do, but remember that this function, in terms of the React lifecycle, is called when the component actually gets rendered, and that this function must return JSX.

Now, where this gets a little odd is when you are talking about functions and events that need to happen during or around the time when you render your component. For example, what if your component needs to call out to some external service? You don't know how long that external service will take to respond to you, so what do you do?

For example, let's say we take a call to an external service that takes 5 to 10 seconds to return data back to us, and we have that inside of our render() call:

render() {

  const data = fetchDataFromExternalService(); // This takes 5-10 seconds to return

  return (<p className="App">Response from service: {data}</p>);

}

When React goes to render the component onto the page, you will see a huge delay before everything else shows up on your page. This creates a really awful user experience, so it really underscores the importance of keeping your render() calls light and not dependent on anything that you can't control the performance of. The good news, however, is that there is a component lifecycle method that is perfect for dealing with these sorts of situations.

componentDidMount()

The componentDidMount() lifecycle method is better suited for functions that take an indeterminate amount of time to load. componentDidMount() is called after render() is called, so you don't get any weird behavior where the component takes a long time before it actually shows up on your page. As a result, this lifecycle method has become the standard for when you want to load anything into the state that is the result of an AJAX call or anything that involves any sort of long-running code.

One thing worth noting, however, is that your calls that modify the state might take a very short amount of time. If this happens, you might sometimes see a flicker effect that occurs when you display a placeholder for things that are only conditionally rendered when the state is updated with data. This is generally considered to be bad practice and you should instead use a placeholder render (such as a little bit of text that says Loading... or displays a spinner when the page is loading).

Another common best practice is to render an element that informs the reader that the page or content is still loading, rather than having the initializing state to be empty. If you do this, it will give the impression that the component has no data, and then when the AJAX call returns, it suddenly has data. This can be confusing for the end user, and generally it's better to be clear about the transitional states in your component rather than having a binary loaded/not loaded state. For example, think about when you use an email client. If it displays No emails received! when you first load the page, and then the load happens, and suddenly you have over a thousand emails in your inbox. Such occurrences can be misleading for the user and they might not trust the information they see. Instead, picture that same email client, but when it is loading, we have rendered an element that informs us that the data is loading. This leads to a much better user experience. Now, the user can trust the information that you are displaying to them.