Computer scienceFrontendReactState

Component Lifecycle

11 minutes read

In React, a component's lifecycle can be thought of as the series of events that happen from the birth of the component (when it's rendered on the DOM) to its death (when it's removed from the DOM). Class components have a built-in lifecycle consisting of several methods automatically called at different stages of a component's life. These methods allow you to perform actions at specific points, such as initializing state, fetching data, updating the DOM, and cleaning up resources. Unlike class components, functional components don't have a built-in lifecycle. However, with the introduction of React Hooks, functional components gained the ability to manage state and perform side effects, simulating some aspects of the component lifecycle. In this topic, you will learn about the component lifecycle, its phases, and methods.

Phases of component lifecycle

Here are the phases of the component lifecycle:

  1. Mounting phase: This phase occurs when a component is created and inserted into the DOM.

  2. Updating phase: This phase occurs when there's a change in a component's state or props.

  3. Unmounting phase: This phase occurs when a component is removed from the DOM.

In functional components, hooks such as useState and useEffect are used to manage these phases. However, in older class components, there are different methods available for use.

Here's a visual representation of the component lifecycle. The detailed explanation follows:

Component lifecycle diagram

Component lifecycle with class components

Let's take an example of a class component and walk through the different lifecycle methods:

import React from 'react';

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
    console.log('Constructor'); // Mounting phase
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    console.log('getDerivedStateFromProps'); // Mounting and updating phase
    return null;
  }

  componentDidMount() {
    console.log('componentDidMount'); // Mounting phase
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log('shouldComponentUpdate'); // Updating phase
    return true;
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log('getSnapshotBeforeUpdate'); // Updating phase
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('componentDidUpdate'); // Updating phase
  }

  componentWillUnmount() {
    console.log('componentWillUnmount'); // Unmounting phase
  }

  incrementCount = () => {
    this.setState(prevState => ({
      count: prevState.count + 1,
    }));
  };

  render() {
    console.log('render'); // Mounting and updating phase
    const { count } = this.state;

    return (
      <div>
        <p>Count: {count}</p>
        <button onClick={this.incrementCount}>Increment</button>
      </div>
    );
  }
}

export default Counter;

In this example, we have a class component called Counter that maintains a count state variable. Let's go through the different lifecycle methods:

  1. Mounting phase:

    • constructor: The constructor method is called when the component is being initialized. It is used to set the initial state and bind event handlers.

    • static getDerivedStateFromProps: This static method is called before rendering and allows you to update the state based on changes in props. It is rarely used in favor of the useState hook in functional components.

    • render: The render method is responsible for returning the JSX that represents the component's UI.

    • componentDidMount: This method is called immediately after the component is mounted and added to the DOM. It is commonly used for side effects like fetching data or subscribing to events.

  2. Updating phase:

    • static getDerivedStateFromProps: This method is also called during the updating phase, allowing you to update the state based on changes in props. It is rarely used in favor of other methods or hooks.

    • shouldComponentUpdate: This method is called before rendering when new props or state are received. It allows you to control whether the component should re-render or not. By default, it returns true, but you can optimize performance by implementing custom logic.

    • getSnapshotBeforeUpdate: This method is called right before the changes from the virtual DOM are reflected in the actual DOM. It allows you to capture information from the DOM before it changes. It is rarely used and often paired with componentDidUpdate.

    • render: The render method is called again to update the component's UI based on the new state or props.

    • componentDidUpdate: This method is called after the component is updated and re-rendered. It is often used for side effects that depend on the updated state or props.

  3. Unmounting phase:

    • componentWillUnmount: This method is called just before the component is unmounted and removed from the DOM. It is used to clean up any resources, such as event listeners or timers, created during the component's lifecycle.

You can observe the order of these lifecycle methods by rendering the above Counter component in a parent component or in the root of your React application. The console logs will show the sequence in which the methods are called during the component's lifecycle.

The result would be similar to this:

Class component lifecycle example

Component lifecycle with functional components

In functional components, the lifecycle methods are replaced by React Hooks, which allow you to manage state and perform side effects. Let's take the same example component and convert it into a functional component using React Hooks:

import React, { useState, useEffect } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('Component mounted');

    return () => {
      console.log('Component unmounted');
    };
  }, []);

  useEffect(() => {
    console.log('Component updated');
  }, [count]);

  const incrementCount = () => {
    setCount(prevCount => prevCount + 1);
  };

  console.log('Component rendered');

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={incrementCount}>Increment</button>
    </div>
  );
};

export default Counter;

Let's look at each phase of the Counter functional component:

  1. Mounting phase:

    • useState: You can use the useState hook to initialize and manage state within a functional component. It allows you to declare a state variable and a function to update it. It replaces the need for a constructor and this.state in class components.

    • useEffect: The useEffect hook lets you perform side effects, such as fetching data, subscribing to events, or manipulating the DOM. It runs after the component has rendered and can be used to mimic the behavior of componentDidMount in class components when the dependency array is empty. In our example, we have two useEffect hooks:

    • The first useEffect hook runs once when the component is mounted, logging "Component mounted" to the console. The empty dependency array [] ensures it only runs once.

    • The second useEffect hook runs whenever the count state variable changes, logging "Component updated" to the console.

  2. Updating phase:

    • useState: Updating the state using the state update function returned by useState will trigger a re-render of the component. Also, when the props change, the component will re-render. This is similar to how a component re-renders when state changes.

    • useEffect: You can specify dependencies for the effect to control when it should run. If any of the dependencies change, the effect will run again. This can be used to mimic the behavior of componentDidUpdate.

  3. Unmounting phase:

    • useEffect: You can return a cleanup function from the effect, which will be executed when the component is unmounted. This can be used to mimic the behavior of componentWillUnmount.

The result for this component would be displayed as follows, and you can also verify this yourself:

Functional component lifecycle example

Conclusion

In conclusion, both class components and functional components in React have their own ways of handling the component lifecycle.

For class components, the lifecycle is managed through a series of predefined methods such as constructor, render, componentDidMount, shouldComponentUpdate, componentDidUpdate, and componentWillUnmount. These methods allow you to control the behavior of the component at different stages of its life, from initialization to updates and unmounting.

On the other hand, functional components leverage React Hooks to handle state and side effects. Hooks like useState and useEffect enable functional components to simulate lifecycle behavior. The useState hook manages state, replacing the need for a constructor and this.state, while the useEffect hook handles side effects and serves as a replacement for lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount.

3 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo