Introducing React Lifecycle Methods

So-called lifecycle methods come in handy to change the default way React renders elements, like if you want to manipulate the DOM directly or compare the previous and the current props. In this post, we’ll examine three of the most common lifecycle methods. Since you can only use lifecyle methods in class components, let’s remind ourselves the difference between class and function components.

Class Components vs Function Components

React lets you use both functions and classes as components but you can only use lifecycle methods inside a class component.

Convert your function component to a class by creating a new class and moving the function body to a function named render() inside the class. For example, this function component:

function MyComponent(props) {
  return <button onClick={props.doSomething}>My Awesome Button</button>;
}

becomes this class component:

class MyComponent extends React.Component {
  render() {
    return <button onClick={this.props.doSomething}>My Awesome Button</button>;
  }
}

Once you’ve got a class component, you can implement a lifecycle method by creating a function inside the class. For example, to implement componentDidMount(), create a function called componentDidMount():

class MyComponent extends React.Component {
  render() {
    return <button onClick={this.props.doSomething}>My Awesome Button</button>;
  }
  
  componentDidMount() {
    // do something here
  }
}

Let’s now look at what some lifecycle methods do.

Managing External State

componentDidMount() is probably the lifecycle method you’ll use the most. It’s where you make HTTP calls and you manipulate the DOM. For example, suppose you want to instantiate the flatpickr date picker inside your component. You must pass the id of a valid DOM element or a reference to the DOM element to the flatpickr() function. You can’t control when React creates or updates the DOM structure for a component, but React guarantees that the DOM structure will exist by the time it calls componentDidMount(). To make sure flatpickr() finds a valid DOM element, store a reference to the DOM element using refs, then initialize flatpickr inside componentDidMount():

class DatePicker extends React.Component {
  componentDidMount() {
    this.picker = flatpickr(this.el, {});
  }

  render() {
    return (
      <input
        ref={input => {
          this.el = input;
        }}
      />
    );
  }
}

The function you pass as the ref prop receives the DOM element behind the <input> React element. Store a reference to the DOM <input> element in the this.el instance variable. In componentDidMount(), pass this.el to flatpickr(). flatpickr() might generate some extra DOM elements and event handlers, so you’ve got to remove them when they’re not needed any more.

Cleaning Up

When React removes your component from the DOM, you need to keep the memory usage in check and destroy the DOM structures you created in componentDidMount(). You can do this inside another lifecycle method called componentWillUnmount(). For example, flatpickr provides a cleanup function called destroy() that you can call inside componentWillUnMount().

  componentWillUnmount() {
    this.picker.destroy();
  }

Let’s now look at how you can futher manipulate React’s rendering cycle, but this time to skip rendering completely.

Boosting Performance

The next lifecycle method, shouldComponentUpdate(), has a different use case. If shouldComponentUpdate() returns false, React won’t re-render the component even if its props or state changed. By skipping re-rendering, you preserve the identical functionality but hopefully improve performance.

Although you’ll find plenty of articles about shouldComponentUpdate(), implementing this method has become a bit superfluous. The most common usage pattern used to be this:

  1. if you use objects as props or state, always replace these objects completely on every update instead of changing the properties inside the same object
  2. then, compare the reference to the previous props and state to the new props and state inside shouldComponentUpdate() and return false if the references are the same.

If that’s what you want to achieve, you can inherit from React.PureComponent instead of React.Component:

class MyComponent extends React.PureComponent

This saves you from implementing shouldComponentUpdate() by hand, but note that comparing by reference only works if you never update an object inside props or state in place.

To improve performance, I believe you should consider reorganizing your props and component hierarchy before using shouldComponentUpdate() or React.PureComponent, unless you’re already committed to always completely replacing any props or state objects. Personally, I try to avoid passing large objects as props through deep React element hierarchies. This has the added benefit of making the code clearer and often requires less radical changes to existing coding practices.

Learning More

Lifecycle methods allow you to customize when React decides to render the elements to the DOM and to perform extra actions before and after rendering.

Even though defaults works just fine most of the time, there are even more special cases that you can handle with lifecycle methods, like rendering a value that depends on the difference between the current and the previous props. For a complete list of lifecycle methods, head over to the official documentation and if you want a complete tutorial on building React applications, including integrating third-party libraries similar to flatpickr, take a look at React for Real.