left-icon

React.js Succinctly®
by Samer Buna

Previous
Chapter

of
A
A
A

CHAPTER 8

Component Lifecycle

Component Lifecycle


Every React component has a story.

The story starts when we define the component class. This is the template that we use every time we want to create a component instance to be mounted in the browser.

Let me tell you the story of a Quote component that we are going to use to display funny, short quotes on a web page. The Quote story begins when we define its class, which might start with a mockup like the following:

Code Listing 55: Quote Component Mockup

class Quote extends React.Component {

  render() {

    return (

      <div className="quote-container">

        <div className="quote-body">Quote body here...</div>

        <div className="quote-author-name">Quote author here...</div>

      </div>

    );

  }

}

The component class is our guide for the markup that should be used to represent a single quote element. By looking at this guide, we know that we’re going to display the quote body and its author’s name. The previous definition, however, is just a mockup of what a quote would look like. To make the component class useful and able to generate different quotes, the definition should be made generic.

 For example:

Code Listing 56: Generic Quote Component

class Quote extends React.Component {

  render() {

    return (

      <div className="quote-container">

        <div className="quote-body">{this.props.body}</div>

        <div className="quote-author-name">{this.props.authorName}</div>

      </div>

    );

  }

}

This template is now ready to be used to represent any quote object, as long as it has a body attribute and an authorName attribute.

Our Quote component story continues; the next major event in its history is when we instantiate it. This is when we tell the component class to generate a copy from the template to represent an actual quote data object.

For example:

Code Listing 57: Quote React Element

<Quote body="..." authorName="..." />

The instantiated <Quote /> element is now full-term and ready to be born. We can render it somewhere (for example, in the browser).

Let’s define some actual data to help us through the next events in our component’s lifecycle.:

Code Listing 58: Quotes Data

var quotesData = [

  {

    body: "Insanity is hereditary. You get it from your children",

    authorName: "Sam Levenson"

  },

  {

    body: "Be yourself; everyone else is already taken",

    authorName: "Oscar Wilde" },

  {

    body: "Underpromise and overdeliver",

    authorName: "Unknown"

  },

  ...

];

To render the first quote in the browser, we first instantiate it with an object representing the first quote in our data: quotesData[0].

Code Listing 59: Instantiating a React Element

var quote1 = quotesData[0];

var quote1Element = <Quote body={quote1.body}

                           authorName={quote1.authorName} />;

// Or using the spread operator

var quote1Element = <Quote {...quote1} />;

We now have an official <Quote /> element (quota1Element), which represents the first object from our quote data.

Let’s take a look at its content.:

Code Listing 60: renderToStaticMarkup

console.log(ReactDOMServer.renderToStaticMarkup(quote1Element));

// Output

<div className="quote-container">

  <div className="quote-body">

    Insanity is hereditary. You get it from your children.

  </div>

  <div className="quote-author-name">

    Sam Levenson

  </div>

</div>

The output will be one big string that represents the first quote in our data, according to the HTML template defined in the component class.

We used renderToStaticMarkup to inspect the content, which works just like render, but does not require a DOM node. renderToStaticMarkup is a useful method if we want to generate static HTML from data. There is also a renderToString method that’s similar, but compatible with React’s Vvirtual DOM on the client. We can use it to generate HTML on the server and send it to the client on the initial request of the application. This makes for a faster page load and allows search engines to crawl your application and see the actual data, not just JavaScript with one empty HTML node.

Note: Applications that leverage the trick of rendering HTML for the initial request are known as Uuniversal Aapplications (or Iisomorphic Aapplications). They use the same components to render a ready HTML string for any client (including Ssearch Eengine bots). Normal clients will also get the static version, and they can start their process with it. For example, if we give React on the client-side a static version generated on the server-side using the same components, React will start by doing nothing at first, and it will update the DOM only when the state changes.

Both renderToString and renderToStaticMarkup are part of the ReactDOMServer library, which we can import from "“react-dom/server"”.

Code Listing 61: ReactDomServer (ES2015 Import Ssyntax)

import ReactDOMServer from "react-dom/server";

// For static content

ReactDOMServer.renderToStaticMarkup(<Quote {...quote1} />);

// To work with React Vvirtual DOM

ReactDOMServer.renderToString(<Quote {...quote1} />);

However, on the client-side, we would like our application to be interactive, so we need to render it using the regular ReactDOM.render method.

Code Listing 62: Component Instance

ReactDOM.render(

  <Quote {...quote1} />,

  document.getElementById("react")

);

A Quote component instance is now in the browser, fully mounted, and is part of the browser’s native DOM.

React has two lifecycle methods that we can use to inject custom behavior before or after a component instance getis mounted in the DOM. These are componentWillMount and componentDidMount.

The best way to understand these lifecycle methods is to define them in our component class and put a debugger line in both of them.

Code Listing 63: Understanding Lifecycle Methods

class Quote extends React.Component {

  componentWillMount() {

    console.log("componentWillMount...");

    debugger;

  }

  componentDidMount() {

    console.log("componendDidMount...");

    debugger;

  }

  render() { ... }

}

If we run this in the browser now, dev -tools will stop the execution for debugging twice.

The first stop will be in componentWillMount. React exposes this method for us to write custom behavior before the DOM of the component instance getis written to the browser. In the following figure, You’ll nnotice how the browser’s document would still be empty at this point.:

ComponentWillMount() debugger line

Figure 5: ComponentWillMount() debugger line

The second stop will be in componentDidMount(). React exposes this method for us to write custom behavior after the DOM of the component instance igets written to the browser. In the following figure, You’ll notice how the browser’s document would show the HTML for our first quote at this point.:

ComponentDidMount() debugger line

Figure 6: ComponentDidMount() debugger line

React exposes other lifecycle methods for updating and unmounting components. Every lifecycle method has specific advantages and use cases. I’ll give one practical example for each method so that you can understand them in context.

componentWillMount()

React invokes this method right before it attempts to render the component instance to its target. This works on both the client (when we use ReactDOM.render) and the server (with ReactDOMServer render methods).

Practical example:

We want to create a log entry in our database every time a quote getis rendered using our Quote component. This should include quotes rendered server-side for sSearch Eengine Ooptimization (SEO) purposes. Since componentWillMount is triggered on both the client and the server, it’s an ideal place to implement this feature.

Assuming the API team wrote an endpoint for us to use, and that we just need to post to /componentLog and send it the name of the component and its used props, and also assuming we have an AJAX library (like jQuery.ajax, for example), we can do something like this::

Code Listing 64: CcomponentWillMount()

componentWillMount() {

  Ajax.post("/componentLog", {

    name: this.constructor.name,

    props: this.props

  });

}

componentDidMount()

React invokes this method right after it successfully mounts the component instance inside the browser. This only happens when we use ReactDOM.render. React does not invoke componentDidMount when we use ReactDOMServer render methods.

componentDidMount is the ideal place to make our component integrate with plugins and APIs to customize the rendered DOM.

Practical example:

The boss wants you to integrate an API to the Quote component to display how popular a quote is. To get the current popularity rate of a quote, you need to hit an API endpoint:

https://ratings.example.com/quotes/<quote-text-here>

The API will give you a number between 1 and 5, which you can use to show the popularity on a five-star scale.

The boss also requested that this feature is not to be implemented server-side because it would slow down the initial render, and the feature should not block the rendering of a quote on the client. The quote should getbe rendered right away, and once we have a value for its stars-rating, display it.

We can’t use componentWillMount here because it is invoked on both server and client render calls. componentdDidMount, on the other hand, getis invoked only on client calls.

Since we need to display the stars-rating number in our component somewhere, and since it’s not part of the component props and ibut s instead read from an external source, we’ll need to make it part of the component’s state to make sure React is going to trigger a re-render of the component when the stars-rating variable gets a value.

We can do something like this:

Code Listing 65: CcomponentDidMount()

componentDidMount() {

  Ajax.get(`https://rating.example.com/quotes/${this.props.body}`)

      .then(starRating => this.setState({ starRating }));

}

Once the quote igets displayed in the browser, we initiate a request to the API, and when we have the data back from the API, we’ll tell React to re-render the component’s DOM (which would now have the stars-rating) by using a setState call.

Note: Be careful about using setState inside componentDidMount, as it usually leads to twice the amount of browser render operations.

Integrating jQuery plugins is another popular task where componentDidMount is a good option, but be careful with that—when we add event listeners to yourthe mounted component’s DOM, we need to remove them if the component igets unmounted. React exposes the lifecycle method componentWillUnmount for that purpose.

To see the rest of the component lifecycle methods in action, let’s add control buttons to our Quotes applications to enable users to browse through all the quotes we have. (sSo far, we’re only showing the first quote).

Let’s create a Container component to host the currently active quote instance plus all the control buttons we need. The only state needed by this Container component will be the index of the current quote. To show the next quote, we just increment the index.

The Container component would be something like:

Code Listing 66: Container Component

class Container extends React.Component {

  constructor(props) {

    super(props);

    this.state = { currentQuoteIdx: 0 };

  }

  render() {

    var currentQuote = this.props.quotesData[this.state.currentQuoteIdx];

    return (

      <div className="container">

        <Quote {...currentQuote } />

        <hr />

        <div className="control-buttons">

          <button>Previous Quote</button>

          <button>Next Quote</button>

        </div>

      </div>

    );

  }

}

And we use it with:

Code Listing 67: Using the Container Component

ReactDOM.render(

  <Container quotesData={quotesData} />,

  document.getElementById("react")

);

This is what we should see in the browser at this point:

One Qquote and Bbuttons

Figure 7: One Qquote and Bbuttons

Now Llet’s now make the buttons work. All we need to do is increment or decrement the currentQuoteIdx in the buttons’ click handlers.

Here’s one way to do that:

Code Listing 68: nextQuote Function

nextQuote(increment) {

  var newQuoteIdx = this.state.currentQuoteIdx + increment;

  if (!this.props.quotesData[newQuoteIdx]) {

    return;

  }

  this.setState({ currentQuoteIdx: newQuoteIdx });

}

Define the nextQuote function in the Container component class.

The if statement is to protects against going beyond the limits of our data—clicking “Previous Quote” on the first quote or “Next Quote” on the last one would do nothing.

Note: The if statement check is the minimum validation that we should do. Buttons should also be disabled if they can’t be clicked. Try to implement that on your own.

 Here’s how to use the nextQuote handler when we click the buttons:

Code Listing 69: Buttons Click Handlers

<button onClick={this.nextQuote.bind(this, -1)}>

  Previous Quote

</button>

<button onClick={this.nextQuote.bind(this, 1)}>

  Next Quote

</button>

The bind call here is basically a fancy way to wrap our nextQuote function with another function, but this new outer function would remember the increment variable value for each button.

Go ahead and try the buttons now. They should work.

Every time we click on the buttons (given that the if statement in the handler is false), we’re updating the DOM for the <Quote /> element. We’re doing this through React by controlling the props passed to the mounted <Quote />.

Here’s what happens in more detail:

  • The user clicks the “Next Quote” button.
  • The <Container /> instance gets a new value for the currentQuoteIdx state.
  • React responds to the state change in <Container /> by triggering its render() function.
  • React computes the new DOM for the <Container /> instance, and that involves re-rendering the <Quote /> instance. Since the currentQuoteIdx was incremented, the currentQuote object would now be different than the one we used previously.
  • In a way, React updates the mounted <Quote /> instance with new props.

During that process, React invokes four lifecycle methods for us to customize the behavior if we need to. Let’s see them in action.:

Code Listing 70: Quote Update Lifecycle Methods

class Quote extends React.Component {

  componentWillReceiveProps() {          

    console.log("componentWillReceiveProps...");

    debugger;

  }

  shouldComponentUpdate() {

    console.log("shouldComponentUpdate...");

    debugger;

    return true;

  }

  componentWillUpdate() {

    console.log("componentWillUpdate...");

    debugger;

  }

  componentDidUpdate() {

    console.log("componentDidUpdate...");

    debugger;

  }

  render() { ... }

}

Refresh your browser now, and note how none of these console.log lines will fire up on the initial render of the first quote. However, when we click Next Quote, we’ll see all of them fire, one by one.

I’ve added debugger lines here for you to see the UI state between these four stages. For the first three, the browser’s DOM will still have the old quote displayed, and once you get to the componentDidUpdate debugger line, you’ll see the new quote in the browser.

Let me explain these methods with practical examples.

componentWillReceiveProps(nextProps)

Whenever a mounted component gets called with a new set of props, React will invoke this method passing the new props as the argument.

Practical example:

You wrote a random even number generator function generateEvenRandomNumber, and you used it in a component TestRun to render a random even number in the browser every second using a setInterval call.

Code Listing 71: TestRun

// Render every second:

<TestRun randomNumber={generateEvenRandomNumber()} />

To test the accuracy of your generator code, you rendered 100 of these <TestRun /> instances in your browser and let the timers run for a while.

You want to make sure that no component getis rendered with an odd number. Instead of watching the components, you can use componentWillReceiveProps to make the component “remember” if it was rendered with an odd number, and how many times this happened.

Code Listing 72: TestRun Component

class TestRun extends React.Component {

  constructor(props) {

    super(props);

    this.state = { badRuns: 0 };

  } 

  componentWillReceiveProps(nextProps) {

    if (nextProps.randomNumber % 2 === 1) {

      // Bad Run. Log it.

      this.setState({ badRuns: this.state.badRuns + 1 });

    }

  }

  render() { ... }

}

Note: React will trigger the componentWillReceiveProps method even if nextProps is identical to currentProps. The Vvirtual DOM operation is what determines if a render to the DOM should actually happen.

shouldComponentUpdate(nextProps, nextState)

This is a special method. If you noticed, when we tested the update lifecycle methods, this was the only one where we returned true.

This method is similar to componentWillReceiveProps, but has some differences:

  • In addition to nextProps, React also passes a nextState object to this method.
  • If we write code that returns false in this method, the update process will be stopped, and the component will not be updated. That’s why we returned true when we tested shouldComponentUpdate previously. Go ahead and test returning false there instead, and see how the “Next” and “Previous” buttons would stop working.

This method can be used to enhance the performance of some React components. If a component only uses its props and state in the render function, and nothing global, for example, we can compare the current props and state with nextProps and nextState in shouldComponentUpdate, and return false if the component is receiving similar values.

Components that only read from props and state in their render functions are known as Ppure Ccomponents. They’re very similar to pure functions in the sense that their return value (the output of render) is only determined by their input values (props and state).

For pure components we can safely do the following:

Code Listing 73: Pure Components

class PureComponentExample extends React.Component {

  shouldComponentUpdate(nextProps, nextState) {

    return notEqual(this.props, nextProps) ||

           notEqual(this.state, nextState);

  }

  render() {

    // Read only from this.props and this.state

    // Don't use any global state

  }

}

notEqual() would be a function that can compare two objects for their keys' values.

Practical example:

You have a component that takes a timestamp prop and renders the date part of it, (and ignoresing the time).

Code Listing 74: Date Component Element

<Date timestamp={new Date()} />

If we’re rendering this component frequently, the only time we would actually want it to update would be tomorrow, so we can short-circuit the update process with shouldComponentUpdate().

Code Listing 75: Date Component shouldComponentUpdate()

class Date extends React.Component {

  shouldComponentUpdate(nextProps, nextState) {

    return this.props.timestamp.toDateString() !==

             nextProps.timestamp.toDateString();

  }

  render() { ... }

}

componentWillUpdate(nextProps, nextState)

When a mounted component receives new props, or when its state changes, React invokes the componentWillUpdate method. This happens right before the render function getis called.

Note that if we customized shouldComponentUpdate and returned false, React will not invoke componentWillUpdate.

We cannot use setState in componentWillUpdate. It’s simply too late for that.

Practical example:

In our quotes application, we’re now updating a single <Quote /> instance to render multiple quotes. The database log entry that we do in componentWillMount is not going to be invoked when we hitclick the Next Quote button. componentWillMount is only gets invoked on the initial render.

For this example, we could use componentWillUpdate to invoke the exact same code we used in componentWillMount.

Code Listing 76: componentWillUpdate() Rreusing cCode

logEntry(component) {

  Ajax.post("/componentLog", {

    name: component.constructor.name,

    props: component.props

  });

}

componentWillMount() {

  logEntry(this);

}

componentWillUpdate() {

  logEntry(this);

}

Note, however, that this method gets invoked every time React re-renders, even if it’s rendering with the exact same props. If we want the log entry to only happen when the props change, we’ll need to introduce an if statement about that in componentWillUpdate.

componentDidUpdate(prevProps, prevState)

React invokes this final method after a component gets is updated and after the changes are synced to the browser. If we need access to the previous props and state, we can read them from the parameters of this method.

Just like componentDidMount, componentDidUpdate is helpful when we want to integrate external libraries with our components, set up listeners, or work with an API.

Practical example:

Our componentDidMount example also applies to componentDidUpdate, given the change we made to render a new quote by updating the props. However, since we’re potentially hitting an external API for this example, we should be careful about doing so directly in componentDidUpdate, because we might be hitting the API endpoint for a stars-rating value that we already have.

One thing we can do for this current example is to simply cache the stars-rating values we read from the API.:

Code Listing 77: CcomponentDidUpdate() rReusing cCode

setStarRating(ci) {

  if (ci.starRatings[ci.props.id]) {

    ci.setState({ starRating: ci.starRatings[ci.props.id] });

    return;

  }

  Ajax.get("https://rating.example.com/quotes/" + ci.props.body)

      .then(starRating => {

        ci.starRatings[ci.props.id] = starRating;

        ci.setState({ starRating });

      });

}

componentDidMount() {

  setStarRating(this);

}

componentDidUpdate() {

  setStarRating(this);

}

Note how we used a component instance variable (ci.starRating) to hold the cache of all API calls. We can use instance variables when we don’t need React to trigger a re-render call when their values change.

There are a lot of similarities between pairs of mounting and updating lifecycle methods, and often you’ll find yourself extracting code into another function and invoking that function from multiple methods (which is what we did in the previous example). However, the separation is helpful sometimes, especially when you want to integrate third-party code,, like jQuery plugins for example, with your initially rendered DOM.

The last lifecycle method you should be aware of is componentWillUnmount.

componentWillUnmount()

React invokes this method right before it attempts to remove a component instance from the DOM.

To see an example of that, put a componentWillUnmount method on the Quote class.:

Code Listing 78: componentWillUnmount()

componentWillUnmount() {

  console.log("componentWillUnmount...");

}

Then try to remove all mounted content from the DOM. ReactDOM has a method for that.

Code Listing 79: UunmountComponentAtNode()

ReactDOM.unmountComponentAtNode(document.getElementById("react"));

This will unmount any React components rendered inside the element passed to it as an argument. We should see the console.log line from componentWillMount in the console.

Practical example:

When we set up listeners or start timers in componentDidMount, we should clear them in componentWillUnmount:.

Code Listing 80: Start and Stop Listeners

componentDidMount() {

  // start listening for event X when triggered by Y

}

componentWillUnmount() {

  // stop listening for event X when triggered by Y

}

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.