React component’s lifecycle.

www.123rf.com

A journey to the React components internals.

In previous posts we covered the basics of React and how to build more complex interfaces composing components. In this post we will continue exploring the features of React’s components. Specifically, the component’s lifecycle.


Think a little bit about what a React component does… Based on what we have covered so far in a single sentence: it describes what to render. We already know that it uses the render() method for this purpose. However having only the render() method may not always suffice our requirements. What if we want to do something before or after the component has rendered or mounted ? What if we want to avoid a re-render?

Looks like we need more control over the stages that a component goes through. The process where all these stages are involved is called the component’s lifecycle and every React component goes through it. React provides several methods that notify us when certain stage of this process occurs. These methods are called the component’s lifecycle methods and they are invoked in a predictable order.

Basically all the React component’s lifecyle methods can be split in four phases: initialization, mounting, updating and unmounting. Let’s take a closer look at each one of them.

Initialization

The initialization phase is where we define defaults and initial values for this.props and this.state by implementing getDefaultProps() and getInitialState() respectively.

The getDefaultProps() method is called once and cached — shared across instances — when the class is created, before any instance of the component are created, hence we can’t rely on this.props here. This method returns an object which properties values will be set on this.props if that prop is not specified by the parent component.

The getInitialState() method is also invoked once, right before the mounting phase. The return value of this method will be used as initial value of this.state and should be an object.

Now we can showcase the previously exposed, implementing a component that shows a value that can be incremented and decremented interactively, basically a counter handled by “+” and “-” buttons.

We configured via getDefaultProps() a “title” property, that if not provided has a default value. Also we set up the initial state of the component “{count: 0}” via getInitialState(). If we run this code and watch the console output you will see the execution order of the methods:

Now we will add to the Counter component the possibility to set up the initial value of this.state.count and the value of the increment/decrement step, but also we will provide sensible defaults.

Note:
We are using partial application here via Function.prototype.bind to get rid of the duplicated code.

Now we have a more customized component!!!

Mounting

Mounting is the process that occurs when a component is being inserted into the DOM. This phase has two methods that we can hook up with: componentWillMount() and componentDidMount().

The componentWillMount() method is the first called in this phase. It’s invoked once and immediately before the initial rendering occurs, hence before React inserts the component into the DOM. It’s very important to note that calling this.setState() within this method will not trigger a re-render. If we add the next code to the Counter component we will see that the method is called after getInitialState() and before render().

getInitialState: function() {...},
componentWillMount: function() {
console.log('componentWillMount');
},
...

The componentDidMount() is the second invoked in this phase, just once and immediately after React inserts the component into the DOM. Now the updated DOM is available for access, which means that this method is the best place for initializing other Javascript libraries that need access to the DOM and for data fetching operations.

Suppose we want to initialize the Counter component with data fetched from an API. We could fetch the data in the Counter’s componentDidMount() method directly, but it seems like it’s too much responsibility for this component. Instead we can do it in a container component as follows:

Note:
Axios is a promise based HTTP client for the browser and Node.js

Updating

There are also methods that will allow us to execute code relative to when a component’s state or properties get updated. These methods are part of the updating phase and are called in the following order:

  1. When receiving new props from the parent:

2. When the state changes via this.setState():

During this phase a React component is already inserted into the DOM. Thus these methods are not called for the first render().

The first one is componentWillReceiveProps(), invoked when a component is receiving new props. We can use this method as an opportunity to react to a prop transition before the render() method is called. Calling this.setState() within this function will not trigger an additional re-render, and we can access the old props via this.props. For example in the Counter component if we want to update the state whenever the parent passes the property “initialCount”, we can do it like this:

...
componentWillReceiveProps: function(newProps) {
this.setState({count: newProps.initialCount});
},
...

The shouldComponentUpdate() method allows us to decide whether the next component’s state should trigger a re-render or not. This method returns a boolean value, which by default is true. But we can return false and the next methods won’t be called:

  • componentWillUpdate()
  • render()
  • componentDidUpdate()

We can use shouldComponentUpdate() method if performance is a bottleneck . Especially with dozens or hundreds of components where the re-render is particularly expensive. For illustration purpose let’s take a look at an example:

In this contrived example whenever a parent passes a “text” property to the TextComponent and is equal to the current “text” prop, the re-render will be avoided.

The componentWillUpdate() method is called immediately before rendering, when new props or state are being received. We can use this as an opportunity to perform preparation before an updates occurs, however is not allowed to use this.setState().

...
componentWillUpdate: function(nextProps, nextState) {
console.log('componentWillUpdate', nextProps, nextState);
},
...

The componentDidUpdate() method is called immediately after React updates the DOM. We can use this method to interact with the updated DOM or perform any action post-render. This method gets two arguments:

  1. prevProps: the previous properties object
  2. prevState: the previous state object

A common case for this method is when we are using a third-party library that needs the rendered DOM to perform its job — e.g. a jQuery plugin — . We initialize the library the first time in the componentDidMount() method but after some prop or state change that triggers a DOM update we need to update as well the third-party library to keep our interface consistent. This must be done within componentDidUpdate() method. To illustrate this, let’s take a look at how to make a wrapper React component for Select2 library:

Unmounting

In this phase React provide us with only one method:

  • componentWillUnmount()

It is called immediately before the component is unmounted from the DOM. We can use it to perform any cleanup we might need, such as invalidating timers or cleaning up any DOM elements that were created in componentDidMount()/componentDidUpdate(). For example in the Select2 component we could use it as follows:

...
componetWillUnmount: function(){
$(this._ref).select2('destroy');
},
...

Conclusions

React gives us the possibility when creating components to declare methods that will be called automatically in certain occasions throughout the component’s lifecycle. Now we have a clear understanding of the role that each component’s lifecycle method plays and the order in which they are called. This gives us the opportunity to perform certain actions when the component is created and destroyed. It also allows us to react to props and state changes accordingly, to easily integrate third-party libraries and to tackle performance optimization problems.

I hope you find this post useful. If so, recommend it please!!!