The component lifecycle
Every React component that is rendered into the DOM goes through a series of steps before and after rendering. As React component developers, we can hook into these steps, called the component lifecycle, in order to perform tasks or check conditions specific to some stage in that lifecycle:
Before a component is mounted, which means placed into the DOM for the first time, React will look at that component's class to see if it has a method called componentWillMount
defined. Should this method exist, React will invoke it. This method is a good place to do things such as set up timers needed by the component or request data the component needs from the server:
componentWillMount() { //Decrement an internal state counter every second setInterval(() => { this.setState({ secondsLeft: this.state.secondsLeft - 1; }); }, 1000); }
The next step in the component's lifecycle is the first render. The render()
method we've seen before. React calls this method and then, the first time, converts the JSX element output to HTML elements and places them in the DOM. In other words, it mounts the component.
Once mounting is complete, the next step in the lifecycle, an optional method called componentDidMount
, is called. This is often an integration point for non-React libraries. With that said, a word of warning: it is generally not a good idea to use libraries that manipulate the DOM alongside React. Remember that React works by keeping a virtual representation of the DOM in memory in order to calculate change sets and apply them. When other libraries are modifying the DOM, it can quickly become out of sync with what React expects. This could, and more often than not, will, lead to errors when React tries to reconcile changes:
componentDidMount() { //Integrate with an external library here }
From here, the component is stable and its lifecycle dormant until one of two things happens. The first thing that could happen is the component's parent could pass it new props
. The second is some event or interval triggers a change in internal state
. These two actions, of course, necessitate a re-render. Before a re-render happens, there are a few other lifecycle methods that will be called.
The update cycle
The first method called during a property update cycle is componentWillReceiveProps
. Here, we not only know that the component is about to receive a new set of properties, but we also can see what those properties are and how they compare to the old ones:
componentWillReceiveProps(nextProps) { //an object of new props console.log(nextProps); //The current (old) props console.log(this.props); }
This lifecycle method is a good place to update state that is somehow derived from props because it is the only update lifecycle method that is not called for both prop and state changes.
This brings us to the next lifecycle method that is called when either props or state are updated: shouldComponentUpdate
. This method is unique among lifecycle methods in that it is the only one that expects a return value. As you may be able to guess, the return value expected is a Boolean. If the method returns true
, the lifecycle continues as we expect it. However, if shouldComponentUpdate
returns false
, the lifecycle is short-circuited here and a re-render does not occur. Within this method, we can see not only the new properties, but also the new state that will be rendered:
shouldComponentUpdate(nextProps, nextState) { if (this.props.uid !== nextProps.uid) { return true; } return false; }
If a component does not define this method, it is always assumed to be true
. React, though, gives you the ability to override this behavior. This can become important in large applications with many components and many layers of component nesting. Using shouldComponentUpdate
, we can fine-tune when a component re-renders in order to enhance the performance of our application. This is important because, while React is good at optimizing renders, rendering is still computationally expensive and excessive rendering can slow down an application to the point where a user can feel stuttering.
If shouldComponentUpdate
returns true
(or is not defined by the component), the next step in the lifecycle is componentWillUpdate
, which is the last step before re-rendering. Here, like in shouldComponentUpdate
, we have access to both the new properties and the new state:
componentWillUpdate(nextProps, nextState) { //Prepare for render! }
At this point, React will call render
on the component again, getting its new JSX representation. It will compare this new JSX to the old JSX in the virtual DOM and create a change set to apply to the real DOM. Once this process is complete, we arrive at the next step of the lifecycle, which is componentDidUpdate
. This method is very similar to componentWillUpdate
, except that it receives the previous properties and state as arguments:
componentDidUpdate(prevProps, prevState) { //Here are the old props console.log(prevProps); //And here are the current (new) props console.log(this.props); }
Now, we've completed the update lifecycle. At this point, once again the component remains dormant until another change in properties or state occurs. This process continues over and over again until the component is removed, or unmounted, from the DOM.
Unmounting the component
Just before a component is removed from the DOM, the final stage of the component's lifecycle will be completed. Here, React calls the optional componentWillUnmount
method, which receives no arguments.
This method is a good place to clean up anything that the component created over the course of its life. For instance, if the component started an interval upon mounting, here would be a good place to stop that interval. In our componentWillMount
example, we showed starting a countdown interval that fired every second after the component mounted. If we store that interval's ID in state, we can then stop the interval when the component is being unmounted:
componentWillMount() { //Save the interval in state this.setState({ tickInterval: setInterval(() => { this.setState({ secondsLeft: this.state.secondsLeft - 1; }); }, 1000); }); } componentWillUnmount() { //Stop the countdown before unmounting clearInterval(this.state.tickInterval); }
While we've gone through and demonstrated how each lifecycle method might be used within a component, it is important to point out that we would very rarely need to use every component lifecycle method in a single component. Remember that each one is optional and need not be defined by the component unless some feature of its functionality necessitates it. In fact, our NewsItem
component does not need any of these lifecycle methods to do exactly what we want.