React Hooks - useEffect
Tags: react, javascript, webdev
Published: 2020-04-11 21:43:48


> ***The useEffect hook lets you perform side effects in function components***

## What are side effects?
Side effects are not specific to React. A side effect is anything that affects something outside the scope of the function/component being executed. Anything that is not the return value is technically a side effect.
### A few common examples of side effects
* ***Data fetching/Network requests***
* ***Setting up a subscription to an external data source***
* ***Manually changing the DOM***
*  ***Accessing the Window object***

## Basic Syntax
The `useEffect` hook accepts two arguments: the side effect callback function, and an optional dependency array of state values to watch for changes. 
```javascript
useEffect(sideEffectFunction, [stateToTrack]);
```

By using this hook, you tell React that your component needs to do something after render. React will remember the effect function you provided, and run it after flushing changes to the DOM and letting the browser paint the screen. 

By default, `useEffect` runs after the first render and after every update. Effects happen after render. React guarantees that the DOM has been updated before it runs the effects. 

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

const Counter = () => {
  const [count, setCount] = useState(0)
  const min = 0
  
  // Register the effect you want to run
  useEffect(() => {
    // Update the document title using the browser API 
    document.title = `The current count is ${count}`
  })
 
  const increment = () => setCount(count + 1)
  const decrement = () => (count > min ? setCount(count - 1) : count)
  const reset = () => setCount(0)

  return (
    <div className='counter'>
      <p className='count'>{count}</p>
	  <div className='controls'>
	    <button type='button' onClick={increment}>
	      Increment
	    </button>
	    <button type='button' onClick={decrement}>
	      Decrement
	    </button>
	    <button type='button' onClick={reset}>
	      Reset
	    </button>
	  </div>
    </div>
  )	
}

export default Counter
```

In the above counter example, we declare the `count` state variable and set its initial value to 0. We then tell React we need to use an effect for updating the document title. 

We pass a function to `useEffect`, which is our effect that we want to be run after the component renders. Inside of our effect, we set the document title using the `document.title` browser API. Remember, accessing the browser API and manipulating the DOM directly is a side effect. 

The above effect is run on every render, including the first one.

## Skipping Effects
The effect hook runs when the component mounts but also when the component updates. In the counter example above, the effect is run on every single render which is ok in this case because `count` is our only piece of state and we want our effect to be run whenever count changes. This is almost never what you want. 

Let's look at an example where not passing an array of dependencies and allowing the effect to be run on every render would cause us some serious trouble.

```javascript
const Repos = () => {
  const  [userName,  setUserName]  =  useState('')
  const  [repos,  setRepos]  =  useState([])
  
  useEffect(() => {
    async function  fetchRepos() {
      const  response  = await fetch(`https://api.github.com/users/${userName}/repos`)
      const  repos  = await response.json()
    
      // our setRepos call tells React to re-render the component.
      // which then calls our useEffect hook again, so on and so forth
      setRepos()
    } 
    fetchRepos().catch(error  =>  console.error(error))
    // this is because we are not passing an array of 
    // dependencies as the second argument to useEffect
  })

  const handleSubmit = (e) => {
    e.preventDefault()
    setUserName(e.target.username.value)
  };
  
  return (
	<>
	  <form onSubmit={handleSubmit}>
        <label htmlFor='username' placeholder='E.g. gaearon'>
	      Enter a Github Username
          <input type='text' id='username' />
        </label> 
        <button type="submit">Fetch Repos</button>
      </form>
      <section  aria-labelledby='repos-label'>
        <h2  id='repos-label'>Github Repositories for {userName}</h2>
        {!repos.length  ? (
          <p>
            <b>Not seeing any repos? Either there are no repos for the user you have provided, they do not exist, or there was an error while fetching. Please try again with a different username.</b>
          </p>
        ) : (
          <ul>
            {repos.map(repo  => (
            <li  key={repo.id}>
              <a  href={repo.html_url}>{repo.name}</a>
            </li>
            ))}
          </ul>
        )}
      </section>
    </>
  );
}
```

The above example is making a network request for an array of Github repositories for a given username, then spits out a list of links pointing to those repos. When the effect is run, it sets our repos state variable, which tells React to re-render our component, which then triggers our effect which tells React to re-render, so on and so forth sending us into a death loop of renders and network requests until either our browser stops responding or we hit our rate limit of 5000 requests to the GitHub API per hour.

So, we don't want to let our effect run after every single render. One option to prevent this death loop is to pass an empty array of dependencies as the second argument to `useEffect`.  This would tell React to only run our effect on the very first render.

```javascript
...
  useEffect(() => {
    async function  fetchRepos() {
      const  response  = await fetch(`https://api.github.com/users/${userName}/repos`)
      const  repos  = await response.json()
    
      setRepos()
    } 
    fetchRepos().catch(error  =>  console.error(error))

  // Passing an empty array of dependencies tells React 
  // to only run our effect on the very first render
  }, [])
  ...
``` 

As you probably guessed, this is also **NOT** what we want as we would like to fetch a new list of repos when we submit our form. With an empty array, submitting the form which updates our `userName` in state, would not make a new request for the updated user's list of repositories as **our effect is only run once, on the very first render.** 

So, we don't want our effect to be run when `repos` value is updated and we also don't want it to only run on the very first render. Our solution is to add `userName` as the only dependency to our effect. 

```javascript
...
  useEffect(() => {
    async function  fetchRepos() {
      const  response  = await fetch(`https://api.github.com/users/${userName}/repos`)
      const  repos  = await response.json()
    
      setRepos()
    } 
    fetchRepos().catch(error  =>  console.error(error))

  // Now our effect will only run if the value of userName in state is updated
  }, [userName])
  ...
``` 

### Here is the full solution to our Repos component.

```javascript
const Repos = () => {
  const  [userName,  setUserName]  =  useState('')
  const  [repos,  setRepos]  =  useState([])
  
  useEffect(() => {
    async function  fetchRepos() {
      const  response  = await fetch(`https://api.github.com/users/${userName}/repos`)
      const  repos  = await response.json()
    
      setRepos()
    } 
    fetchRepos().catch(error  =>  console.error(error))
  }, [userName])

  const handleSubmit = (e) => {
    e.preventDefault()
    setUserName(e.target.username.value)
  };
  
  return (
	<>
	  <form onSubmit={handleSubmit}>
        <label htmlFor='username' placeholder='E.g. gaearon'>
	      Enter a Github Username
          <input type='text' id='username' />
        </label> 
        <button type="submit">Fetch Repos</button>
      </form>
      <section  aria-labelledby='repos-label'>
        <h2  id='repos-label'>Github Repositories for {userName}</h2>
        {!repos.length  ? (
          <p>
            <b>Not seeing any repos? Either there are no repos for the user you have provided, they do not exist, or there was an error while fetching. Please try again with a different username.</b>
          </p>
        ) : (
          <ul>
            {repos.map(repo  => (
            <li  key={repo.id}>
              <a  href={repo.html_url}>{repo.name}</a>
            </li>
            ))}
          </ul>
        )}
      </section>
    </>
  );
}
```

## useEffect & Cleanup

Sometimes, we want to  **run some additional code after React has updated the DOM.**  Network requests, DOM mutations, and logging are common examples of effects that don’t require cleanup. We say that because we can run them and immediately forget about them. 

Anything that we set up that is recurring such as an interval, subscription, websocket connection, etc. needs to be cleaned up when the component unmounts. 

Let's add a twist to our counter component...

```javascript
const Counter = () => {
  const [count, setCount] = useState(0)
  	
  // Log the count to the console after 3 seconds
  // This effect is not cleaning up after itself	
  useEffect(() => {
	setInterval(() => {
      console.log(`Count: ${count}`)
    }, 3000)
    // not returning a cleanup function here
  }, [count])
...
```

The problem with not cleaning up our `setInterval()` is that each time the component re-renders, we register another interval. If we were to update the `count` from 0 to 1, after three seconds, 0 would be logged to the console then 1 would be logged to the console, then 0, then 1 and so on.. 

This is because there are now two intervals from two separate renders logging the value of count to the console. Each interval has access to the value of `count` from its respective render. When the component first rendered, the value was 0, so an interval was started to log 0 every three seconds. When we updated `count` to 1, React triggered another render, then our effect was called, registering another interval to log the new value of `count` to the console every 3 seconds. 

The first interval was never cleared, so now we have two intervals running at the same time. 

To avoid this, we need to return a cleanup function from `useEffect` for our interval. 

```javascript
const Counter = () => {
  const [time, setTime] = useState(new Date())
  	
  // Log the count to the console after 3 seconds
  useEffect(() => {
	const id = setInterval(() => {
      console.log(`Count: ${count}`)
    }, 3000)
    // Return a function to clear our interval when the component unmounts
    return () => clearInterval(id)
  }, [count])
...
```

`setInterval` returns a number which is the id of that interval. We set that number to a variable which we pass to the `clearInterval` function returned from our effect. Now, when `count` is updated and our component is unmounted before re-mounting to the DOM, we clean up the previous interval. With this in place, only the current value of `count` will be logged to the console 3 seconds after updating its value. 

## Recap
* **The `useEffect` hook lets you perform side effects in function components;**
* **A side effect is anything that affects something outside the scope of the function/component being executed;**
* **The `useEffect` hook accepts two arguments: the side effect callback function, and an optional dependency array of state values to watch for changes;**
* **By not passing a dependency array to our effect, it will be run on every single render;**
* **If we pass an empty array, the effect will only be run once, on the very first render;** 
* **To avoid an infinite loop of renders and effect calls make sure you are only passing the state values that your effect depends on in the dependency array;**
* **Intervals, subscriptions or anything that is meant to be recurring, should be cleaned up by returning a cleanup function from your effect;**

Thanks for reading!