React Hooks Series: useEffect
Tags: react, javascript, webdev, beginners
Published: 2020-08-19 19:58:15

## Introduction

{% codesandbox l7vuy %}

A reminder that all code examples come directly from the [Timer CodeSandbox](https://codesandbox.io/s/timer-clone-l7vuy?file=/src/Timer.js) I put together. You are encouraged to open it, fork it, play around with the code, follow along, whatever helps you learn best!

In my first article in the React Hooks Series I wrote about the useState hook. This iteration will focus on useEffect (my Timer example calls the useRef hook first, but I think it makes more sense to understand what's happening with useEffect before we tackle useRef).

# Part Two - useEffect

What is useEffect?

From the [React docs](https://reactjs.org/docs/hooks-effect.html): "The Effect Hook lets you perform side effects in function components:"

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

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

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
```
>If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

>Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update.

In my own words: useEffect runs anytime something changes. This could be the user interacting with a form, a button, etc. State changing, like `counter` in my Timer app, counting down every second or `start` being set from `false` to `true` when the user hits START. Or the component itself is loaded (mounted) or unloaded (unmounted) from the screen. 

### Getting started

Add useEffect to our React import.

```javascript
import React, { useState, useEffect } from "react";
```
Let's take a look at the first useEffect function.
```javascript
useEffect(() => {
    if (start === true) {
        pauseTimer.current = counter > 0 && setTimeout(() => setCounter(counter - 1), 1000)
      }
      return () => {
        clearTimeout(pauseTimer.current)
      }
}, [start, counter, setCounter])
```
A lot going on here. Remember that we set state of `start` to `false`. Therefore, even if our Timer component updates, this useEffect() will not run until `start === true`. 

Inside of our `if (start === true)` conditional block is the meat and potatoes of our useEffect (and really the whole point of the app!):

`pauseTimer.current = counter > 0 && setTimeout(() => setCounter(counter - 1), 1000)`

However, we are going to ignore `pauseTimer.current` for now (this logic is tied to our PAUSE button and the useRef hook).

Let's examine the following:
When `start === true` run the code inside the block:
`counter > 0 && setTimeout(() => setCounter(counter - 1), 1000)`

If `counter > 0` run: `setTimeout(() => setCounter(counter - 1), 1000)`

(Remember that we use `setCounter(input)` to update `counter`. Let's say a user selects 10 seconds, `input === 10` and when user hits submit, then `counter === 10`.)

This is where the magic happens. Counter is 10. [setTimeout](https://www.w3schools.com/jsref/met_win_settimeout.asp) accepts a function to run and a time in milliseconds. When that time expires, setTimeOut will run the function. In our case setTimeout accepts our `setCounter()` function and will run after 1000 milliseconds (1 second). `setCounter(counter - 1)` will run after 1 second, changing 10 to 9.

Every single time the state of ANYTHING changes/updates, useEffect is called. Therefore, when `counter` changes from 10 to 9, useEffect is called again! Is 9 greater than 0? YES! Then run the code to the right of `if counter > 0` which happens to be our setTimeout function. This process happens until our `if counter > 0` is no longer `true`. When `counter === 0`, `counter` is no longer greater than 0, or `false` and will skip over the setTimeout to the right.

Next, take a look at this.
```javascript
 return () => {
   clearTimeout(pauseTimer.current)
 }
```
What is this return function inside our useEffect?

This has to do with cleanup. I had to deal with this in my [GIF FIT](https://gif-fit.netlify.app/) app (the inspiration for this entire series of React hooks articles), where I am dealing with several setTimeouts (6 in total) running in sync.

They are separate components in my app. When one timer ended, another began. I quickly discovered that if you do not "clean up" certain functions inside a useEffect, you will get something called a "memory leak". Basically, my setTimeouts were still running in the background, taking up memory. NOT GOOD. 

Luckily, useEffect has a simple solution. It accepts a final function which can clean up effects from the previous render and when the component finally unmounts. The above function inside our useEffect is effectively killing the setTimeout and avoiding any memory leaks! Cool, huh?

### Putting it together

```javascript
{
  start === false && counter !== null && counter !== 0
  ? 
  <button style={{fontSize: "1.5rem"}} onClick={handleStart}>START</button> 
  : 
  null
}

{
  start === true && counter !== 0
  ? 
  <button style={{fontSize: "1.5rem"}} onClick={handlePause}>PAUSE</button> 
  : 
  null 
}
```

In Part One, useState(), I showed how we rendered the START button if `start === false && counter !== null && counter !== 0`

Which gives us access to `onClick={handleStart}`

*user clicks start*

```javascript
const handleStart = () => {
    setStart(true)
}
```

`start === true` 

*state changes and useEffect() runs*

Our setTimeout decrements `count` by one

*state changes and useEffect runs again*

Repeat this action until `count === 0` and is no longer greater than 0. 

Yay! Our timer is working!

I'm about to blow your mind. Maybe. Did you know you can have multiple useEffect functions in the same component? Once my timer is finished (`counter === 0`), I needed a way to reset the state of `start` back to `false`

Enter a second useEffect!

```javascript
useEffect(() => {
    if (counter === 0) {
      setStart(false)
    }
}, [counter, setStart])
```

Pretty straight forward. When useEffect detects that `counter === 0` it will call `setStart(false)` which means `start === false`.

This is a good time to talk about what `[start, counter, setCounter]` and `[counter, setStart]` does at the end of our two useEffects. These are dependencies that we are calling inside our useEffects, and we are explicitly telling our useEffects that when one of these change, do your thing!

You don't always need that array to wrap up a useEffect, but it's a good habit to get into. And if you want a useEffect to run only once, you place an empty array `[]` at end of your useEffect function, because there aren't any dependencies, it won't know to run when state changes again.

## Wrapping up

Thank you for reading Part Two of my React Hooks series. If you missed Part One, please check it out and let me know what you think. 

Part Three will focus on the useRef hook and I am really excited about this one. The useRef hook is my least comfortable in terms of use and understanding. But so far it has been one of my favorites to work with. I am really impressed by how much the useRef hook can accomplish. 

As always, thank you for making it this far and I look forward to any questions, comments, corrects, and even criticism!

HAPPY CODING