Complete Guide to useEffect Hook in React
Tags: react, hooks
Published: 2021-06-16 03:22:09

# What is useEffect?

`useEffect` is a [react hook](https://reactjs.org/docs/hooks-intro.html) that lets you run _side effects_ inside a functional component. Side effects can be any operation that does not interfere with the main execution of the component, like:

- Directly manipulating the DOM.
- Fetching data from an API in the background.
- Running a function after a certain amount of time using `setTimeout` or at each interval using `setInterval`.

# The syntax

`useEffect` has the following syntax:

```jsx
useEffect(
  () => {
    // the callback function which has the side effect you want to run
    return () => {
      /* this is an optional cleanup callback,
       which will be called before the next render */
    }
  },
  [
    /* this an optional array of dependencies. 
    The useEffect callback runs only when these dependencies change*/
  ]
)
```

It might look overwhelming at first sight. Do not worry!
In this tutorial, we will break it into pieces and learn all the practical combinations and applications of `useEffect`.

# The simplest `useEffect`

Since the only mandatory parameter of an `useEffect` is the callback function, let's write one with just the callback:

```jsx
import { useEffect, useState } from "react"

function App() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    console.log("Running useEffect")
    document.title = `You clicked ${count} times`
  })

  console.log("Running render")
  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>Click Me</button>
    </div>
  )
}

export default App
```

In the above example, we are having a button, when clicked will increment the `count` by 1. Then we have written a `useEffect` hook where we console log "Running useEffect" and update the title of the page (direct DOM manipulation) with the number of clicks.

If you run the code and open the browser console, you should be able to see the logs as shown below:

![Simple UseEffect](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y9511on4tqzr8ujukgp7.png)

As you could see, first the component will be rendered and then the effect will run. Now, if you click on the button, you will see that the component is rendered again (since the state has changed) and the title of the page is updated with the number of clicks.

![Simple useEffect clicked](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t3ikdvnr9s3yggtsuqw8.png)

From this, we can infer that the `useEffect` (with only a callback function) will run after each render.

# Infinite Loops

Since `useEffect` runs after every render, what if the effect inside `useEffect` causes the component to re-render? That is, if the `useEffect` updates the state of the component, wouldn't it cause the component to re-render? Wouldn't it cause the useEffect to run again, and so on causing an infinite loop? **Yes**!

Let's see it using an example:

```jsx
import { useEffect, useState } from "react"

function App() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    console.log("Running useEffect")
    setCount(count + 1)
  })

  console.log("Running render")
  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>Click Me</button>
    </div>
  )
}

export default App
```

If you open the console, you will see the code is executed indefinitely:

![Infinite loop](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ru8zneb3rv6kp8ntqi7.png)

If you look closely, React is showing a warning:

`Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.`

This clearly says that you are updating a state inside the useEffect, which is causing the component to re-render.

## How to avoid infinite loops and still update the state inside the useEffect?

This is where the dependency array comes into the picture. We will learn about how to use them in the upcoming sections.

# Fetching data with `useEffect`

Let's build a small app where we fetch the bitcoin price and display it. Before implementing the app, let's add some styles to `index.css`:

```css
body {
  margin: 10px auto;
  max-width: 800px;
}
.App {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.refresh {
  display: flex;
  align-items: center;
}

.refresh-label {
  margin-right: 10px;
}

.switch {
  position: relative;
  display: inline-block;
  width: 60px;
  height: 34px;
}

.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  -webkit-transition: 0.4s;
  transition: 0.4s;
}

.slider:before {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  -webkit-transition: 0.4s;
  transition: 0.4s;
}

input:checked + .slider {
  background-color: #2196f3;
}

input:focus + .slider {
  box-shadow: 0 0 1px #2196f3;
}

input:checked + .slider:before {
  -webkit-transform: translateX(26px);
  -ms-transform: translateX(26px);
  transform: translateX(26px);
}

/* Rounded sliders */
.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}
```

We will use the endpoint [https://api.coincap.io/v2/assets/bitcoin](https://api.coincap.io/v2/assets/bitcoin) to fetch the bitcoin price. Now if you are using async-await syntax to fetch the data, your code will look like:

```jsx
useEffect(async () => {
  try {
    const response = await fetch("https://api.coincap.io/v2/assets/bitcoin")
    const result = await response.json()
    const bitcoinPrice = (+result?.data?.priceUsd).toFixed(2)
    setPrice(bitcoinPrice)
  } catch (error) {
    console.log("error", error)
  }
}, [])
```

If you use this code, you will get a warning from React telling us not to make `useEffect` callbacks async. How to tackle this problem? The error message itself suggests having another async function and call it inside the `useEffect` callback.

![useEffect Async Await](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ab2adp5bktmh237etyaa.png)

So if we update our code accordingly it will look like the following:

```jsx
import { useEffect, useState } from "react"

function App() {
  const [price, setPrice] = useState()

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch("https://api.coincap.io/v2/assets/bitcoin")
        const result = await response.json()
        const bitcoinPrice = (+result?.data?.priceUsd).toFixed(2)
        setPrice(bitcoinPrice)
      } catch (error) {
        console.log("error", error)
      }
    }
    fetchData()
  }, [])

  return (
    <div className="App">
      <h2>{price && `Bitcoin Price: $${price}`}</h2>
    </div>
  )
}

export default App
```

You might observe that we are passing an empty array as a dependency (the second argument to `useEffect`). This is to ensure that the `useEffect` runs only once when the component is mounted and not when the component is updated or re-rendered. As you might have guessed correctly, `useEffect` with an empty dependency array is the same as that of [componentDidMount](https://reactjs.org/docs/react-component.html#componentdidmount) lifecycle method in a class component.

Now if you run the app, you should be able to see the bitcoin price being displayed:

![Bitcoin Price](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9xmu44qhhhg6bdf6itm0.png)

# Running it when certain states change

Since the bitcoin price changes every moment, let's make our app more interesting and fetch the price every 5 seconds!

```jsx
import { useEffect, useState } from "react"

function App() {
  const [price, setPrice] = useState()

  useEffect(() => {
    let interval
    const fetchData = async () => {
      try {
        const response = await fetch("https://api.coincap.io/v2/assets/bitcoin")
        const result = await response.json()
        const bitcoinPrice = (+result?.data?.priceUsd).toFixed(2)
        setPrice(bitcoinPrice)
      } catch (error) {
        console.log("error", error)
      }
    }
    fetchData()

    interval = setInterval(() => {
      fetchData()
    }, 5 * 1000)
    return () => {
      clearInterval(interval)
    }
  }, [])

  return (
    <div className="App">
      <h2>{price && `Bitcoin Price: $${price}`}</h2>
    </div>
  )
}

export default App
```

As you may see, we have added a cleanup call back, which will clear the interval, so that it is cleared before the next render and does not run indefinitely and cause memory leakage. You will find more significance to this in the next section.

Now if you run the app and see the network tab, you will see the call happening every 5 seconds and the price getting refreshed:

![Fetch Price Every 5 seconds](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rmzsni898i0g7skkru3e.png)

Let's not stop here, let's add a toggle button to turn off and on the auto refresh:

```jsx
import { useEffect, useState } from "react"

function App() {
  const [price, setPrice] = useState()
  const [autoRefresh, setAutoRefresh] = useState(true)

  useEffect(() => {
    let interval
    const fetchData = async () => {
      try {
        const response = await fetch("https://api.coincap.io/v2/assets/bitcoin")
        const result = await response.json()
        const bitcoinPrice = (+result?.data?.priceUsd).toFixed(2)
        setPrice(bitcoinPrice)
      } catch (error) {
        console.log("error", error)
      }
    }

    if (!price) {
      // Fetch price for the first time when the app is loaded
      fetchData()
    }

    if (autoRefresh) {
      interval = setInterval(() => {
        fetchData()
      }, 5 * 1000)
    }

    return () => {
      clearInterval(interval)
    }
  }, [autoRefresh, price])

  return (
    <div className="App">
      <h2>{price && `Bitcoin Price: $${price}`}</h2>
      <div className="refresh">
        <div className="refresh-label">Auto refresh:</div>
        <label className="switch">
          <input
            type="checkbox"
            checked={autoRefresh}
            onChange={e => {
              setAutoRefresh(e.target.checked)
            }}
          />
          <span className="slider round"></span>
        </label>
      </div>
    </div>
  )
}

export default App
```

As you can see we have added a state called `autoRefresh`, which will be set to `true` or `false` based on the toggle status of the slider. Also, we have added 2 conditions, one to check if the price is present or not and load the price when it is not present.
Another, to check if the `autoRefresh` is enabled, then only run the logic to fetch price every 5 seconds. Since we need `useEffect` to be executed every time the value of `price` and `autoRefresh` changes, we have added it to the dependency array.

![Auto Refresh](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ed7f8p8fv7myjy9rod48.png)

The cleanup function will get executed before the next render so that, when we set the `autoRefresh` to `false`, the interval will be cleared and data will not be fetched any further.

The difference between the cleanup function and `componentWillUnmount` is that the cleanup function runs before every re-render and `componentWillUnmount` runs only when the whole component is unmounted (towards the end of the component lifecycle). You can read more on why they are different [here](https://reactjs.org/docs/hooks-effect.html#explanation-why-effects-run-on-each-update).

# General trivia on useEffect

- `useEffect` needs to be inside the functional component like any other React hook.
- One component can have as many `useEffect` as required. React will make sure that they are clubbed together and executed (wherever possible).
- Like how state variables can be part of the dependency array, you can have the props as well in the dependency array.
  Make sure that you are adding only the required dependencies, adding unnecessary dependencies will cause unwanted execution of the effect.
- If you miss adding a dependency, react will show a warning to help you avoid bugs:
![Missing Dependency](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/at04cedy8rh49ywtl8gk.png)

# Source code and demo

You can download the source code [here](https://github.com/collegewap/react-useeffect) and
view a demo [here](https://react-useeffect.vercel.app/).