BlogReactJS

How to use React useEffect like componentDidMount

Written by Codemzy on April 6th, 2022

Moving over to ReactJS hooks? Good choice! But how do you use useEffect like componentDidMount? And how do you clean up your code afterwards? Learn how to create custom componentDidMount and componentWillUnmount hooks with useEffect.

Back in the days when React components were a class, you could add lifecycle methods to run code when components mount and unmount. Here's an example from the ReactJS docs:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  //...

componentDidMount is useful for running some code when a component mounts. For example, if a Billing component mounts, you could fetch the user's order records.

But now that hooks are all the rage (and yes, I love hooks), maybe you don't use class components anymore. But the need for lifecycle methods remains.

Luckily, ReactJS provide us with the useEffect hook that can do that.

Recreating componentDidMount with the useEffect hook

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

function DidMount() {
  const [mounted, setMounted] = useState("not mounted");

  // Replaces componentDidMount
  useEffect(() => {
    setMounted("mounted")
  }, []);
  return (
    <div>
      <p>The component is {mounted}. Obviously!</p>
    </div>
  );
};

Ok, so that’s a pretty ridiculous example. If the components are rendered then of course it’s mounted. You don’t need some state to tell you that. But the important part is here…

useEffect(() => {
  // do whatever you need to do when the component mounts
}, []);

And that’s a pretty solid hook for running something one time. In other words, replacing the old componentDidMount lifecycle method.

This could be an API call to fetch some data for the component or to check what menu the user has access to, for example.

By passing the empty array argument [] after the function, you’re telling useEffect to just run this one time, when the component mounts.

If you pass other dependencies in this array, you can get your useEffect to run multiple times, but since we're just recreating componentDidMount here, we will keep it empty.

Creating a custom componentDidMount hook

Just for fun, here’s how a custom hook called useDidMount would look.

import React, {useEffect} from 'react';

function useDidMount(callback) {
  // Replaces componentDidMount
  useEffect(() => {
    callback();
  }, []);
};

The custom hook takes a function as an argument and calls it when the component mounts.

You could use this hook in your components like this:

import React from 'react';
import useDidMount from './hooks/useDidMount';

function MyComponent(props) {
  useDidMount(function() {
    // do something
  });
};

Running the hook before render

Strictly speaking, our ReactJS useEffect hook calls the function when the component renders. 9 times out of 10 this is probably what you want. But componentDidMount used to run before render.

If you're making changes to the DOM here, you might get an initial flicker or shift in the page layout due to the change happening after the render.

If you truly need to replicate componentDidMount you can switch out useEffect with useLayoutEffect to run your function before rendering.

import React, {useLayoutEffect} from 'react';

function useBeforeRender(callback) {
  // runs before render
  useLayoutEffect(() => {
    callback();
  }, []);
};

What about unmounts?

But what about if you need to know when a component unmounts?

Say you have an API request or some other async action that takes time to respond that you can’t cancel. By the time you get a response, the user might navigate away and unmount the component.

Now that you have recreated componentDidMount, let's not forget about componentWillUnmount. This is where you can cancel whatever callbacks or requests are running when your component dismounts.

You don't want to leave redundant code running when your component is no longer around. You need to clean it up!

Recreating componentWillUnmount with the useEffect clean up

In ReactJS classes, you would stop any code in componentWillUnmount. You can recreate componentWillUnmount by passing a cleanup function to useEffect.

useEffect(() => {
  // do whatever you need to do when the component mounts here.
  // …
  // clean up
  return () => {
    // cancel whatever you are doing here.
  };
}, []);

Let’s say you have a setInterval in your useEffect that runs every minute to get fresh data. You would cancel that setInterval in the cleanup function.

function Timer(props) {
  const [date, setDate] = useState(new Date());

  useEffect(() => {
    let tick = function() {
      setDate(new Date());
    }
    let timerID = setInterval(tick, 1000);
    // clean up
    return () => {
      clearInterval(timerID);
    };
  }, []);

  //...
  
};

Creating a custom componentWillUnmount hook

I think it makes more sense now to have your code together in one useEffect hook rather than separate componentDidMount and componentWillUnmount functions.

But if you did want to create a custom hook for componentWillUnmount, here’s how it could look.

import React, {useEffect} from 'react';

function useWillUnmount(callback) {
  // Replaces componentDidMount
  useEffect(() => {
    // clean up
    return () => {
      callback();
    };
  }, []);
};

The benefits of useEffect

I would avoid creating these custom hooks and just useEffect inside your components. I think it is so much cleaner and clearer than the old lifecycle methods.

The great benefit useEffect has over the old lifecycle methods is that you can keep your related code together.

Let's say you run a timer and listen to a socket when your component mounts. You need to clean up both these things when you unmount. Because you can keep both the mounting and unmounting together, it's easier to see when you have (or haven't) cleaned up after yourself!

// Timer
useEffect(() => {
  let tick = function() {
    setDate(new Date());
  }
  let timerID = setInterval(tick, 1000);
  // clean up
  return () => {
    clearInterval(timerID);
  };
}, []);

// API listener
useEffect(() => {
  chatroom.subscribe();
  // clean up
  return () => {
    chatroom.unsubscribe();
  }
}, []);