UseEffect and its class-based equivalent lifecycle methods

Introduction

We all know that the components in React undergo three main phases namely - mount, update & unmount. The mount phase is when a component initially renders on the screen for the first time. The update phase is when a component prop or state variable changes and affects the rendering accordingly. The unmounting phase is when a component disappears(is removed) from the screen.

Now that we have the basic lifecycle idea of a component, let's compare the lifecycle method in the class component to the useEffect hook.

Equivalency

To begin with, please remember the following, the hook equivalent of class lifecycle methods are as below:

  1. componentDidMount - useEffect with empty dependency array
  2. componentDidUpdate - useEffect with no dependency array
  3. componentWillUnmount - useEffect with empty dependency array & clean up function

CSB with live example

In the lives examples of CodeSanBox, we have an App component & a Counter component. The App contains a toggle button to show/hide the Counter. The Counter has the logic to increment the count. Observe the console logs to see the behavior.

Example with class lifecycle methods

Example with useEffect

Class lifecycle methods

The name of the phases suggest the lifecycle methods that we have for class components.

  1. For mounting, we have componentDidMount()
  2. For updating, we have componentDidUpdate()
  3. For unmounting, we have componentWillUnmount()

Let's go through the example to see this in action. Now we will see how these lifecycle methods behave one by one.

componentDidMount

The componentDidMount method runs only once when a component is rendered for the first time, that is when it is mounted.

import "./styles.css";
import React from "react";

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      show: true
    };
  }
  hide() {
    this.setState({ ...this.state, show: !this.state.show });
  }
  render() {
    return (
      <div>
        <button onClick={() => this.hide()}>toggle</button>
        {this.state.show && <Counter />}
      </div>
    );
  }
}
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0
    };
  }
  componentDidMount() {
    console.log("Mounted.");
  }
  increment() {
    this.setState({ ...this.state, counter: this.state.counter + 1 });
  }
  render() {
    return (
      <>
        <p>
          <button onClick={() => this.increment()}>+</button>
          Counter = {this.state.counter}
        </p>
        <br></br>
      </>
    );
  }
}
export default App;

Try running this and you see that it runs once after the initial render & observe the console.

componentDidUpdate

As the name suggests, it looks for the ways in which a component can be updated to run this method. There are 2 ways in which a component can update, one is - a change in props and the second one is - a change in state variables. When either change is observed this method is called. Add this method to the Counter class to see it in action & increment the counter. Here the counter state variable gets updated and we see that this triggers the componentDidUpdate lifecycle method.

 componentDidUpdate() {
    console.log("Updated.");
  }

componentWillUnmount

This method is run when a component gets unmounted, in this case when we toggle to hide the Counter component, this lifecycle method runs.

  componentWillUnmount() {
    console.log(`Unmounted, counter ${this.state.counter}`);
  }

Now that we have seen the lifecycle methods, I am sure that you were able to relate these methods with different variants of useEffect. If not, let me show you down here.

useEffect equivalent methods

useEffect with empty dependency array

This method runs in a functional component only after its first render, that is when a component is mounted for the first time. This is similar to the componentDidMount method in the class lifecycle.

Check out the same example as above but using a functional component.

import { useEffect, useState } from "react";
export default function App() {
  const [show, setShow] = useState(true);
  const hide = () => {
    setShow((prev) => !prev);
  };
  return (
    <div>
      <button onClick={() => hide()}>toggle</button>
      {show && <Counter />}
    </div>
  );
}
function Counter() {
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    console.log("Mounted");
  },[]);

  const increment = () => {
    setCounter((c) => c + 1);
  };
  return (
    <>
      <p>
        <button onClick={() => increment()}>+</button>
        Counter = {counter}
      </p>
      <br></br>
    </>
  );
}

useEffect with no dependency array

This method runs on every state/prop change. It is understood from the empty dependency array that it should run with respect to any state/prop changes. Here since we are incrementing the counter on every click of the button, the state changes and useEffect runs. This is similar to the componentDidUpdate lifecycle method of the class.

To see this in action modify the useEffect as below:

useEffect(() => {
    console.log("Updated");
  });

or

useEffect(() => {
    console.log("Updated");
  },[count]);

Note: Please mind that it can be empty array of dependency or provide proper dependency state variables/props to see the correct behavior.

useEffect with empty dependency array & clean up function

We identify a clean up function in useEffect by the return statement in useEffect. This clean up function runs whenever the counter component gets unmounted. From our example, this runs when we toggle to hide the counter, which is nothing but unmounting the counter from the screen. This is similar to the componentWillUnmount lifecycle method of the class.

To see this in action modify the useEffect as below & click on toggle button.

 useEffect(() => {
    console.log("Mounted");
    return (()=>{
      console.log(`Unmounted`)
    })
  },[]);

Conclusion:

We have hooks that provide us with the same potential that the class components do and make it easier for us to use hooks and functional components. Hope this was helpful.