React Component Lifecycle Methods

☕️☕️ 9 min read

Image

In this article, we are going to explore React lifecycle methods. As for me, if you want to work with the React this knowledge is crucial because you need to be able to implement component logic in the right place.

I'm going to talk about React version 16.4+. But before we will dive in, let's discuss versions difference. If you are completely new to React, I believe you can skip this section and return to it later.

React Versions Difference

From the React 16.3 and above a few lifecycle methods were deprecated. They are still available to use with the prefix UNSAFE_, but will be removed in the next major release.

  • componentWillMount renamed to UNSAFE_ComponentWillMount. You can use the constructor to replace this method.
  • componentWillReceiveProps renamed to UNSAFE_ComponentWillReceiveProps. You can use static getDerivedStateFromProps instead.
  • componentWillUpdate renamed to UNSAFE_ComponentWillUpdate. You can use getSnapshotBeforeUpdate instead.

There is also one more difference between 16.3 and ^16.4:

  • static getDerivedStateFromProps
    In the version 16.3 getDerivedStateFromProps is called only once new props are received, but starting from the 16.4 this method is called every time regardless what caused the update (new props, setState(), forceUpdate()).

Phases of the React component Lifecycle

Image

This image represents React ^16.4 Components Lifecycle. It would be good if you could spend some time to study it, I also recommend you to return to this image during the article reading, so it will be easier to understand and to remember many things.

As we can see from the image, React component goes through the following phases:

Mounting

This is the birth of our component. It means that the component goes through this phase during its initialization and first insertion into the DOM.

1. constructor
constructor(props) {}

This is just a class constructor, and it's the first method that is called when the component is created. The constructor is called only once in the whole component lifecycle.

When to use: initialize state, initialize properties, create refs, create methods bindings.

class ComponentDemo extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      loading: false,
      items: [],
    };

    this.onClick = this.onClick.bind(this);

    this.wrapperRef = React.createRef();
  }
}

If you don't know why do we use super(props), I recommend you to check this article: Why Do We Write super(props)?.

if you use class field proposal it might not be necessary to use the constructor.

class ComponentWithoutConstructor extends React.Component {
  // Create ref without constructor
  wrapperRef = React.createRef();

  // Create State without constructor
  state = {
    loading: false,
    items: [],
  };

  // Use arrow function for callbacks to prevent
  // Function​.prototype​.bind() usage.
  onClick = () => {...}
}

2. static getDerivedStateFromProps
static getDerivedStateFromProps(props, state) {}

The goal of this method is to make sure that state and props are in sync.

This method is static, so it doesn't have access to this and it should avoid any side effects. Instead, it's expected from you to return an object that will be merged with the state, or null if nothing to update.

When to use: update state based on props.

class ComponentDemo extends React.Component {
  static getDerivedStateFromProps(props, state) {
    if (props.loading !== state.loading) {
      return { loading: props.loading };
    }

    // Return null if the state hasn't changed
    return null;
  }
}

Our returned object is similar to if we call this.setState in other non-static lifecycle methods.

this.setState({ loading: props.loading });
In the current phase (mounting) it's also possible to use the constructor instead of this method.

3️. render

This is the only required method in every react class component. In the same time, it's the most used lifecycle method; you will spend a lot of time here.

render() {
  return <p>Hello, {this.props.name}</p>
}

As you can see in the example above, the render() method returns JSX that will be used to update the DOM. It can return JSX or null if nothing to display.

You should not change your state during rendering. React requires the render() method to be pure (it doesn't modify the state, doesn't directly interact with the browser, and returns the same result for the same props and state).

When to use: return component representation using JSX.

4️. componentDidMount

This method is called right after our component did mount (was inserted into the tree). It's the right time to do API calls.

async componentDidMount() {
  try {
    const userDetials = await axios.get(`${apiUrl}/user?ID=12345`);
    this.setState({ details: userDetials });
  } catch(err) {
    this.setState({ error: true });
  } finally {
    this.setState({ loading: false });
  }
}

When to use: API calls, DOM manipulation, integration with 3rd-party libraries that need DOM access.

Updating

This phase starts when our component receives new props or if the current state was changed.

It's also possible to manually run the component updating phase by using this.forceUpdate() method. Note that in this case the shouldComponentUpdate method invocation will be ignored.

1️. static getDerivedStateFromProps

The behavior of this method is the same as described above. You can update your state based on props here.

It's not recommended to keep your state updated based on props, as it makes your component difficult to think about. Try to think first if you really need this inside the state, or you can just derive functionality from the props directly because getDerivedStateFromProps should be used in rare cases.


2️. shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {}

Once component receives new props or a new state it has to be updated. With the help of shouldComponentUpdate, we can tell react that we don't want to this when we think that this is not needed.

The shouldComponentUpdate has access to two arguments that represent next props and next state. We need to return boolean to tell React if we want to update the component, by default it's always true.

Usage: This method should be used only for performance optimization.

You can consider using built-in React.PureComponent instead of manually writing shouldComponentUpdate.

3️. render
render() {}

Now, our component rerenders to returns updated JSX.

4. getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState) {}

This method is called right before our component is ready to be inserted into the DOM. Consequently, it's the last time when you can access the DOM before it gets updated with the rerendered component.

In this method we have access to the previous props and previous state arguments. You can also get the current state and current props through this. Any value that you return here will be passed as a parameter to a componentDidUpdate (next method in this phase).

In many cases, you will not need this method, but sometimes it's very handy. Let's imagine that we have a chat app where new messages appear in the bottom. If user scrolled to the bottom of the list it would be good to keep this scroll position even if the list of messages is growing, so that the user would not need to scroll to the new messages manually to see them. To solve this, we can detect if the current scroll position is the bottom of the list and then return true or false, so that the componentDidUpdate can handle it and adjust scroll position if needed.

When to use: capture information from the DOM before it's changed, for example, scroll position or cursor selection.

Even though this method isn't static it's recommended to return a value that will be passed to the componentDidUpdate instead of changing the state.

getSnapshotBeforeUpdate(prevProps, prevState) {
  if (prevState.messages.length < this.state.messages.length) {
    const listRef = this.listRef.current;
    // Here we detect if user scroll position is at the very bottom and ruturn boolean
    return listRef.scrollTop + listRef.offsetHeight >= listRef.scrollHeight;
  }

  return null;
}

5️. componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot) {}

This method is called right after the component gets updated inside the DOM.

In the componentDidUpdate we have access to the three arguments: previous props, previous state, and snapshot (data passed from the getSnapshotBeforeUpdate).

When to use: the purpose of the componentDidUpdate is similar to the componentDidMount. It's a good idea to do API calls, or 3rd-party libraries updating here.

Let's resume solving the issue I've described in the getSnapshotBeforeUpdate section. We are interested in the snapshot only if it equals true, it means that the user was in the bottom of the list before component was updated, and we need to update scroll position to keep the user at the bottom after

componentDidUpdate(prevProps, prevState, snapshot) {
  if (snapshot === true) {
    const listRef = this.listRef.current;
    // Adjust the position so scroll will be at the bottom again
    listRef.scrollTop = listRef.scrollHeight - listRef.offsetHeight;
  }
}

Unmounting

In this phase the component is not needed anymore and it will be unmounted from the DOM.

1️. componentWillUnmount
componentWillUnmount() {}

This is the last method in the React component lifecycle. It's executed right before our component removal from the DOM. This method should be used to perform the necessary cleanups.

When to use: clear timers, cancel network requests, destroy 3rd party UI library elements, and so on...

You can consider it as a reverting of things we did inside componentDidMount or componentDidUpdate.

Further Reading: