React useEffect hook with code examples

So you learned how to use React useState and how to emulate the traditional setState in a functional React component.

But now you might be scratching your head and asking yourself, “How do I emulate a React component lifecycle?”

Don’t worry, in this article will go over:

  • What is React useEffect
  • How to use React useEffect
  • How to replicate the componentDidMount lifecycle
  • How to replicate the componentWillUnmount lifecycle
  • How to create self-sufficient code for componentDidUpdate lifecycle

So if you’re not familiar with how to use it, read a previous article that covers about it; Introduction to React hooks.

Come back when you’re ready!

What is React useEffect?

React useEffect is a function that gets executed for 3 different React component lifecycles.

Those lifecycles are componentDidMount, componentDidUpdate, and componentWillUnmount lifecycles.

Basic usage of useEffect


import React, { useState } from 'react';

const App = () => {
  const [message, setMessage] = useState('Hi there, how are you?');

  return <h1>{message}</h1>
};

export default App;

In the code example above, I’m using React useState to save a message.

I’m then grabbing my state variable, message, and I’m printing it to the screen.

React useState output example

But now I want to useEffect to change the message a second after the component has mounted.


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

const App = () => {
  const [message, setMessage] = useState('Hi there, how are you?');

  useEffect(() => {
    console.log('trigger use effect hook');

    setTimeout(() => {
      setMessage("I'm fine, thanks for asking.");
    }, 1000)
  })

  return <h1>{message}</h1>
};

export default App;

I’ve imported useEffect from the React library and I’ve added my effect under the useState line.

useEffect accepts a function as it’s first argument. This function handler will take care of any side effects you like when it gets run.

The function is a callback function after one of the React component lifecycle has been triggered.

React useEffect output

It worked! But there’s a problem. Take a look at the console log. The effect got triggered twice.

This behavior is not optimal, because if you have multiple effects or have new prop values being tossed from a parent component, it may trigger the effect multiple times.

This may cause inconsistency, weird side-effects, or freezing your app from an infinite loop.

Let’s optimize this code a little bit more.

React useEffect: The componentDidMount hook

The goal now is to execute the setMessage function only on the componentDidMount lifecycle.

That way if the App component receives any new props or if the state changes, the effect won’t be triggered again.


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

const App = () => {
  const [message, setMessage] = useState('Hi there, how are you?');

  useEffect(() => {
    console.log('trigger use effect hook');

    setTimeout(() => {
      setMessage("I'm fine, thanks for asking.");
    }, 1000)
  }, []);

  return <h1>{message}</h1>
};

export default App;

Can you see the difference from the old example?

If you haven’t caught it yet, I added an empty array bracket ([]) as a second argument to the useEffect hook function.

If you take a look at the console log it only shows “trigger use effect hook” once.

Here’s an example output with another console log message that says, “App component rendered,” after the effect function.

The second console message should only execute when the render lifecycle gets triggered.

If we take a look at the console log again, we can see the order of the lifecycles that it went through.

  1. Rendered lifecycle
  2. componentDidMount lifecycle
  3. Rendered lifecycle

This is the normal behavior that you would see in a traditional React class component.

By running an empty array [] as a second argument, you’re letting React know that your useEffect function doesn’t depend on any values from props or state.

This will help you avoid the componentDidUpdate lifecycle.

React useEffect: The componentWillUnmount hook

I showed an example how to avoid a trigger from a componentDidUpdate lifecycle with useEffect.

But what if you have code that needs to get cleared up on a componentWillUnmount cycle?

How do we replicate a componentWillUnmount lifecycle?

P.S. this lifecycle is also known as, the cleanup in a React hook function.

In the next example I will demonstrate a use case where you’ll need to clean up your code when a component will unmount.


const WindowWidthSize = () => {
  const [windowWidthSize, setWindowWidthSize] = React.useState(0);

  React.useEffect(() => {
    function handleResize(e) {
      const { width } = document.body.getBoundingClientRect();

      setWindowWidthSize(Math.ceil(width));
    }

    window.addEventListener('resize', handleResize)
  }, []);

  return (
    <h1>
      The window size {windowWidthSize} pixels
    </h1>
  )
};

In the image above, I have created a new function component called, WindowWidthSize.

The objective of this component is to print out the width size of the current window.

As you see can see, I’m using useState to keep track of the width size.

And right below it I’m adding a window event listener on the resize event.

So every time the user resizes the browser, it will get the new width, save it into state, and print out the new width size.

Show window width in React functional component

Okay, so here we have another React component that uses the component WindowWidthSize, and it has a magical button.

When a user clicks this magical button, the WindowWidthSize component will vanish before your eyes.

Great, the code works. And if you see on the bottom right there is a blue highlight section called Window.

The blue highlight shows the window event listener I have added.

When I click on the magical button, the WindowWidthSize component will no longer exist. But there’s a small problem.

The window event listener is still lingering around.

Situations like these are bad because this is what causes memory leaks in your app.

And you don’t want to be greedy with your customers limited bandwidth.

Let’s use the clean up technique that useEffect gives us, to get rid of this lingering window event.


const WindowWidthSize = () => {
  const [windowWidthSize, setWindowWidthSize] = React.useState(0);

  React.useEffect(() => {
    function handleResize(e) {
      const { width } = document.body.getBoundingClientRect();

      setWindowWidthSize(Math.ceil(width));
    }

    window.addEventListener('resize', handleResize);

    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return (
    <h1>
      The window size {windowWidthSize} pixels
    </h1>
  )
};

I added a return function inside the useEffect function.

And inside the return function I’m removing the even listener that I originally added.

When a functional component un-mounts the logic inside the return function will get executed.

So remember to clean up your code if necessary by returning a function inside the useEffect function.

React useEffect: The componentWillUpdate hook

By default useEffect will trigger anytime an update happens to the React component.

This means if the component receives new props from it’s parent component or even when you change the state locally, the effect will run again.

In case you need the effect to trigger on a componentDidUpdate lifecycle, you want to try and make it as self-sufficient as possible.

If you don’t, you will run into an infinite loop of updates, and just run your computer hot.


const Counter = () => {
  const [counter, setCounter] = React.useState(0);

  React.useEffect(() => {
    setCounter(c => c + 1);
  }, []);

  return (
    <div style={{textAlign: 'center'}}>
      <h1>Counter: {counter}</h1>
    </div>
  );
};

Right above is a simple counter app. All it does is print a number to the user.

P.S. useState also accepts functions as an argument. This might be a better choice if you want a more accurate representation of the previous data.

I want this counter to increase on every second.


const Counter = () => {
  const [counter, setCounter] = React.useState(0);

  React.useEffect(() => {
    const s = setInterval(() => {
      setCounter(c => c + 1);
    }, 1000);
  }, []);

  return (
    <div style={{textAlign: 'center'}}>
      <h1>Counter: {counter}</h1>
    </div>
  );
};
React counter output

This is good, but not good enough!

If you have multiple setStates inside this component or even receiving new props, this can throw the useEffect hook off track.

Causing your app to not be synchronized or just freeze.


const Counter = () => {
  const [counter, setCounter] = React.useState(0);

  React.useEffect(() => {
    const s = setInterval(() => {
      setCounter(c => c + 1);
    }, 1000);

    return () => clearInterval(s);
  }, [counter]);

  return (
    <div style={{textAlign: 'center'}}>
      <h1>Counter: {counter}</h1>
    </div>
  );
};

Now it’s more self-sufficient.

One, I’ve added a clean up function to clear the interval whenever the componet will unmount.

Two, I’ve added the counter variable inside the array bracket that’s found in the second argument of the useEffect function.

This tells React to only trigger the effect when counter is a different value.

If counter has not changed in value, the effect won’t execute.

This is helpful because you can safely add multiple useEffects, setStates, or even pass down new prop values, and it won’t desynchronize your counter component.

Using Async and Await in React useEffect

React.useEffect does NOT allow you to add async in the callback function.


// This will cause your React application to break!
React.useEffect(async() => {
  // ... stuff
}, []);

React is expecting a regular function and not a promise.

But if you’d like to use async/await you can move your code into it’s own function, and invoke it inside React.useEffect().


async function fetchCats() {
  try {
    const response = await fetch('url/to/cat/api');
    const { cats } = await response.json();
    console.log(cats) // [ { name: 'Mr. Whiskers' }, ...cats ]
  } catch(e) {
    console.error(e);
  }
}

React.useEffect(() => {
  fetchCats();
}, []);

Conclusion

React.useEffect is a basic hook that gets triggered on a combination of 3 React component lifecycles:

If you’re planning to use React hooks you must know how to execute your effect on the right time.

Otherwise you might run into some problems for your users.

I like to tweet about React and post helpful code snippets. Follow me there if you would like some too!