Overview
React components encapsulate parts of the UI. Your full React application UI is rendered as a tree of many nested components. Depending on the flow of the applications, individual components need to perform some tasks before and after rendering as well as before and after updating.
Finally, cleanup and error handling is also important. React provides a slew of lifecycle methods that you can override and inject your own logic at the right place. In this tutorial you will learn about the lifecycle of a React component from cradle to grave, what methods are available at each stage, and when it is appropriate to override them.
Note that I use the modern ES6 classes style in this tutorial.
The PopularBar
I will use a component called the PopularBar to illustrate all the lifecycle methods and how they behave. The full source code is available on GitLab.
The popular bar contains two other components called ClickCounter
. Each ClickCounter
component contains a button with an emoji and displays the number of times it was clicked added to the count property it receives from its host. Here is the render()
method of ClickCounter
:
render() { return (<span className='padded' onClick={() => { let clickCount = this.state.clickCount + 1 this.setState({clickCount: clickCount}) }}><button>{this.props.emoji}</button> {this.getTotal() < 100 ? this.getTotal() : "99+"}</span> ); }
The PopularBar component renders two ClickCounter components with thumbs-up and thumbs-down emojis. Note that if the "show" prop is false, it renders an empty div. This will be important later when we discuss mounting and unmounting.
render() { if (!this.props.show) { return (<div />) } return ( <div className="padded" style={this.props.style}><ClickCounter emoji={thumbsup} count={this.props.upCount} /><ClickCounter emoji={thumbsdown} count={this.props.downCount} /></div> ) }

Mounting
React components exist when they are rendered by a parent component or by the root application. But before a component can be rendered, it needs to be constructed (once only) and mounted into the virtual DOM (every time it is added to the virtual DOM).
The order of events is that first the component is constructed, then the componentWillMount()
method is called, the component is mounted into the virtual DOM, and then the componentDidMount()
is called. That gives you plenty of opportunities to perform different types of initialization.
Constructor
The constructor for a component will be called once per application (if you refresh the page in your browser then it's considered a new application). Here is the constructor for the PopularBar component. It doesn't really do anything except call super()
, which is required, and log to the console.
class PopularBar extends Component { constructor() { super() console.log('--- PopularBar constructor is here!') }
The constructor for ClickCounter initializes its clickCount
state to zero:
class ClickCounter extends Component { constructor(props) { super(props) this.state = { clickCount: 0 } console.log(props.emoji + '=== ClickCounter constructor is here!') }
This is a perfect example of an initialization that has to be done once per application. If a ClickCounter component is mounted multiple times, it should retain its click count.
ComponentWillMount
The componentWillMount()
method is called before the component is mounted, so there is no component yet. In general, there isn't too much that can be done at this stage, unless you have some special initialization that happens every time the component is mounted, but even that can wait to the componentDidMount()
method.
Here are the method implementations for PopularBar and ClickCounter:
// PopularBar componentWillMount() { console.log('--- PopularBar will mount. Yay!') } // ClickCounter componentWillMount() { console.log(this.props.emoji + '=== ClickCounter will mount. Yay!') }
You can call this.setState()
here if you want. The props obviously are not accessible.
ComponentDidMount
Here, the component is already mounted, and you can perform any work that needs to access the component in the context of the virtual DOM. Here are the method implementations for the PopularBar and the ClickCounter. The component already exists, so its properties (props) can be accessed and displayed.
componentDidMount() { console.log('--- PopularBar did mount. upCount: ' + this.props.upCount + ', downCount: ' + this.props.downCount) } // ClickCounter componentDidMount() { console.log(this.props.emoji + '=== ClickCounter did mount. count: ' + this.props.count) }
To summarize the mounting section, let's see the order of events across the PopularBar and the two ClickCounter components it contains. For your convenience, I display the emoji for each ClickCounter, so they can be distinguished.
--- PopularBar constructor is here! --- PopularBar will mount. Yay! 👍=== ClickCounter constructor is here! 👍=== ClickCounter will mount. Yay! 👎=== ClickCounter constructor is here! 👎=== ClickCounter will mount. Yay! 👍=== ClickCounter did mount. count: 5 👎=== ClickCounter did mount. count: 8 --- PopularBar did mount. upCount: 5, downCount: 8
First, the PopularBar is constructed and its componentWillMount()
method is called. Then, the constructor and componentWillMount()
methods of each ClickCounter component are called, followed by the componentDidMount()
calls to both ClickCounter components. Finally, the componentDidMount()
method of PopularBar is called. Overall, the flow is nested where all the sub-components must be fully mounted before their containing component is fully mounted.
Updating
Once the component is mounted, it can be rendered. Every now and then, the state of the component or the props it receives from its container may change. Such changes lead to re-rendering, but the component gets a chance to be notified and even control if rendering should happen or not.
There are four methods involved in the update process, and I'll cover them in order.
ComponentWillReceiveProps
The componentWillReceiveProps()
method is called when new props are received from the container. You have access to the current props via this.props
and to the next props via the nextProps
parameter. Here is the componentWillReceiveProps()
method of ClickCounter.
componentWillReceiveProps(nextProps) { console.log(this.props.emoji + '=== ClickCounter will receive props. ' + 'next props: ' + nextProps.count) }
You have the opportunity here to check what props have changed and modify the state of the component if you wish. It's OK to call this.setState()
here.
ShouldComponentUpdate
The shouldComponentUpdate()
is a key method. It is called when either new props are received (after componentWillReceiveProps()
is called) or after the state of the component is modified elsewhere. If you don't implement this method then the component will re-render every time.
But if you implement it and return 'false', then the component and its child components will not be rendered. Note that if the state of child components is modified, they will be re-rendered even if you always return 'false' from the parent's shouldComponentUpdate()
.
You get access here to the next props and the next state, so you have all the information needed to make a decision. The ClickCounter component displays 99+ when its count exceeds 99, so it needs to update only if the count is less than 100. Here is the code:
shouldComponentUpdate(nextProps, nextState) { let currTotal = this.getTotal() let shouldUpdate = currTotal < 100 console.log(this.props.emoji + '=== ClickCounter should ' + (shouldUpdate ? '' : 'NOT ') + 'update') return shouldUpdate }
ComponentWillUpdate
The componentWillUpdateMethod()
is called after the component shouldComponentUpdate()
only if shouldComponentUpdate()
returned true. At this point, you have both the next props and the next state. You can't modify the state here because it will cause shouldComponentUpdate()
to be called again.
Here is the code:
componentWillUpdate(nextProps, nextState) { console.log(this.props.emoji + '=== ClickCounter will update' + ' nextProps.count: ' + nextProps.count + ' nextState.clickCount: ' + nextState.clickCount) }
ComponentDidUpdate
Finally, after the rendering, the componentDidUpdate()
method is called. It's OK to call this.setState()
here because the rendering for the previous state change has been completed already.
Here is the code:
componentDidUpdate() { console.log(this.props.emoji + '=== ClickCounter did update') }
Let's see the update methods in action. I will cause two types of updates. First, I'll click the thumbs up button to trigger a state change:
--- PopularBar constructor is here! PopularBar.js:10 --- PopularBar will mount. Yay! PopularBar.js:14 👍=== ClickCounter constructor is here! 👍=== ClickCounter will mount. Yay! 👎=== ClickCounter constructor is here! 👎=== ClickCounter will mount. Yay! 👍=== ClickCounter did mount. count: 5 ClickCounter.js:20 👎=== ClickCounter did mount. count: 8 ClickCounter.js:20 --- PopularBar did mount. upCount: 5, downCount: 8 👍=== ClickCounter should update 👍=== ClickCounter will update nextProps.count: 5 nextState.clickCount: 1 👍=== ClickCounter did update
Note that the nextState.clickCount
is 1, which triggers the update cycle. The next update will be caused by the parent passing new props. To facilitate this, I'll add a little function that triggers every 5 seconds and increments the count by 20. Here is the code in the main App component that contains the PopularBar. The change will propagate all the way down to the ClickCounter.
class App extends Component { constructor() { super() this.state = { showPopularBar: true, upCount: 5, downCount: 8 } } componentDidMount() { this.timer = setInterval(this.everyFiveSeconds.bind(this), 5000); } everyFiveSeconds() { let state = this.state state.upCount += 20 this.setState(state) }
Here is the output. Note that the ClickCounter willReceiveProps()
method has been called and the nextState.clickCount
remains at zero, but nextProps.Count
is now 25.
--- PopularBar constructor is here! --- PopularBar will mount. Yay! 👍=== ClickCounter constructor is here! 👍=== ClickCounter will mount. Yay! 👎=== ClickCounter constructor is here! 👎=== ClickCounter will mount. Yay! 👍=== ClickCounter did mount. count: 5 👎=== ClickCounter did mount. count: 8 --- PopularBar did mount. upCount: 5, downCount: 8 👍=== ClickCounter will receive props. next props:25 👍=== ClickCounter should update 👍=== ClickCounter will update nextProps.count: 25 nextState.clickCount: 0
Unmounting and Error Handling
Components can be unmounted and mounted again, and there can be errors during the lifecycle of a component.
Component Will Unmount
If a component is not rendered by its container, it is unmounted from the virtual DOM and the unmounted component's componentWillUnmount()
method is called. The PopularBar will not render its ClickCounter sub-components if the show prop is false. The App component renders the PopularBar and passes the show prop based on the checkbox.
Here is the App's render()
method:
render() { return (<div><h1>Popular Bar</h1><label><input type='checkbox' defaultChecked={this.state.showPopularBar} ref='showPopularBar' onChange={() => this.setState( {showPopularBar: !this.state.showPopularBar}) } /> Show popular bar</label><PopularBar show={this.state.showPopularBar} upCount={this.state.upCount} downCount={this.state.downCount} /></div> ) }
When the user deselects the checkbox, the PopularBar is still rendered, but it doesn't render its child components, which get unmounted. Here is the code and the output:
componentWillUnmount() { console.log(this.props.emoji + '=== ClickCounter will unmount :-(') } Output: 👍=== ClickCounter will unmount :-( 👎=== ClickCounter will unmount :-(
There is no componentDidUnmount()
method because there is no component at this point.
ComponentDidCatch
The componentDidCatch()
method was added recently in React 16. It is designed to help with an error during rendering that previously resulted in obscure error messages. Now, it is possible to define special error boundary components that wrap any child component that may throw errors, and the error boundary component will be rendered only if the error occurred.
Conclusion
React components have a well-defined lifecycle, and special methods let you interject your logic and take actions, control the state at a very fine-grained level, and even handle errors.
Most of the time this is not required, and you can just pass props around and implement the render()
method, but it's good to know that in more specialized circumstances you will not be left staring at a black box.
Over the last couple of years, React has grown in popularity. In fact, we have a number of items in the marketplace that are available for purchase, review, implementation, and so on. If you’re looking for additional resources around React, don’t hesitate to check them out.