React useEffect Hook Flow
Tags: react, javascript, webdev, beginners
Published: 2021-07-04 07:19:27

It is important to understand the core concept of Hooks in React Components. This will increase our confidence with usage of hooks and help us understand what is actually happening inside our React components. 

This post is to increase your understanding of flow of hooks in a react component with exclusive focus on the most confusing `useEffect` hook.

As always, let's start with *Just Javascript*

Take a look at the function below, which returns a string

```javascript
function App(){
  return 'Hello World';
}

const text = App();
console.log(text); // logs 'Hello World'
```

We are storing the value returned from `App` function in variable `text` and displaying it in the console. We know that Javascript is **single threaded** and can execute only one line at a time. The flow of execution is top-to-bottom.

When we execute the code, this is what would happen

1. The Javascript engine first sees a function declaration from line *1 to 3* 
2. Then goes to line number *5* where it sees a function being called. 
3. Then JS engine **calls** that function and assigns the value returned from that function into the 
`text` variable. 
4. In the next line the text is displayed in the console.

Now that we understand the flow of Javascript in general, let's explore the `useEffect()` hook in a react component and explore when it is called and in what order.

## React useEffect

Let's explore useEffect in React on three Lifecycle phases of react component.

1. Mount
2. Update
3. Unmount


## useEffect on Mount

Take a look at the react component below

```jsx
function App(){
  React.useEffect(() => {
    console.log('useEffect Ran!')
  }, []);

  return(
    <div>Hello, World!</div>
  )
}
```

When you scan through this code and find the useEffect with empty `[]` dependencies, you would have guessed that this hook runs only on mount (exactly like `componentDidMount`). Yes, you are right, it runs just on the mount. so you would get this in console

```bash
useEffect Ran! 
```

Lets see an example with a dependency in useEffect,

```jsx
function App() {
  const [count, setCount] = React.useState(0);

  React.useEffect(() => {
    console.log("Count Changed");
  }, [count]);

  return (
    <button
      onClick={() => {
        setCount((c) => c + 1);
      }}
    >
      {count}
    </button>
  );
}
```

This is the classic counter example, when we scan the react component and find the useEffect with `[count]` dependency we would think this would run when the `count` changes. 

So, on the first render the count is 0 and not _changed_, when you click the button, the `count` would change, thus calling the useEffect hook right? lets check it out! 

This is what would be logged on the first mount of the component.

```bash
Count Changed
```

Whaaaat? We didn't even click the button but the useEffect ran! Why? 

Hooks are side-effects, and would be used mostly for performing any side-effects in the component, and the common side effect would be **data fetching**.

When compared to class Lifecycle methods, mentioning any dependency in a hook would make that hook similar to `componentDidUpdate`. If you have `componentDidUpdate` it would still be called on the mount phase! 

This is how the hooks are designed to work. No matter how many dependencies you specify and how many hooks you create, *every hook would be called on the mount phase of the component*.

> If you are curious to know why the hooks are designed in this way, take a look at [Fun with React Hooks](https://youtu.be/1jWS7cCuUXw) in which Ryan Florence live codes `useEffect` hook and explains why hooks should only be called in the top level of react component

After the mount phase is completed, our useEffect in the above counter example would be called whenever the `count` changes. 

```jsx
React.useEffect(() => {
  console.log("Count Changed");
}, [count]);
```

So, the takeaway from this section is

> Every hook in a component would be called on the mount phase (with or without the dependencies specified).

## useEffect on Unmount

Now let's look at another example below with the Unmount behaviour.

```jsx
function Child() {
  React.useEffect(() => {
    console.log("Child useEffect Ran!");

    return () => {
      console.log("cleanUp of Child useEffect Ran!");
    };
  }, []);

  return <div>Hello, From Child!</div>;
}

export default function App() {
  const [showChild, setShowChild] = React.useState(false);

  React.useEffect(() => {
    console.log("useEffect Ran!");

    return () => {
      console.log("cleanUp of useEffect Ran!");
    };
  }, []);

  return (
    <div>
      <div>Hello, World!</div>
      {showChild ? <Child /> : null}
      <button
        onClick={() => {
          setShowChild((b) => !b);
        }}
      >
        {showChild ? "Hide" : "Show"} Child
      </button>
    </div>
  );
}
```

Our Parent `App` component renders a `Child` component which has useEffect with a **cleanup** function. This cleanup would be executed when the child component unmounts. So, When you render the component and toggle on the Hide/Show child button, You would get the corresponding logs as expected.

![Cleanup function in react child](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ofolnslzu9c4ecblyi9f.gif)

If you have 3 useEffects in same component and all does return a cleanup function, then, when the component is unmounted, all the cleanup functions would be called.

Lets see that in action below

```jsx
function Child() {
  React.useEffect(() => {
    console.log("No Dependency!");

    return () => {
      console.log("cleanUp of No Dependency Ran!");
    };
  });

  React.useEffect(() => {
    console.log("Empty Dependency!");

    return () => {
      console.log("cleanUp of Empty Dependency Ran!");
    };
  }, []);

  return <div>Hello, From Child!</div>;
}
```

and the output is

![Multiple return from use effects](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r62g2emq24jvlvyxkql2.gif)

The takeaway is

> When the component is unmounted, cleanup function from all the useEffects would be executed. 

In comparison to class components, where we only have one `componentWillUnmount` this is the only part that would be executed on the unmount phase of that component.

## useEffect on Update

Here comes the interesting part, when you have specified a dependency and if the effect **re-runs** because of any change in the specified dependencies, it would **execute the cleanup functions before** executing the hook.

Let's see this behaviour with an example. Open up the console section, and play around with the buttons.

{% codesandbox react-useeffect-hooks-flow-zokln %}

> You can play around with the [useEffect flow sandbox](https://codesandbox.io/s/react-useeffect-hooks-flow-zokln) to see when each effect is getting called and its order.

On the first mount, we see both the useEffects of `App` running, and when you click on the Increment count button, before running the _no deps hook_, the cleanup function is executed.

```bash
▶️ App Render Start 
🛑 App Render End 
 App: useEffect no deps Cleanup 🧹
🌀 App: useEffect no deps 
```

Similarly, when you click on *Show Child* button, before running the _no deps hook_ of App, the cleanup is executed. 

```bash
▶️ App Render Start 
🛑 App Render End 
       ▶️ Child Render Start 
       🛑 Child Render End 
 App: useEffect no deps Cleanup 🧹
       🌀 CHILD: useEffect empty [] 
       🌀 CHILD: useEffect no deps 
🌀 App: useEffect no deps 
```

As seen above, [from React v17](https://github.com/facebook/react/pull/17925), the cleanup of parent's effects are executed **even before executing child component's useEffect**(s). 

Below GIF is the full rundown from the sandbox. We can see the cleanup functions are executed before the execution of hook on the update/re-render phase. I have highlighted the cleanups with bigger fonts to notice it easily.

![Flow of Hooks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6vt7wfu3xkqg8u5qj7bh.gif)

The key takeaway is, 

> React, when re-running an useEffect, it executes the clean up function before executing the hook.

The full picture of the flow of hooks can be understood from this [flow-chart by donavon](https://github.com/donavon/hook-flow)

![Flow of Hooks](https://raw.githubusercontent.com/donavon/hook-flow/master/hook-flow.png)

I hope this post helps you with understanding of flow of `useEffect` hook.

To Summarise

> 1. Every hook in a component would be called on the Mount phase (with or without the dependencies specified).
> 2. When the component is unmounted, cleanup function from all the useEffects are executed. 
> 3. React, when rerunning an useEffect, it executes the clean up function before executing the hook.


----

Big thanks to:

- [Epic React by Kent.C.Dodds](https://epicreact.dev/)
- [A Complete Guide to useEffect by Dan Abramov](https://overreacted.io/a-complete-guide-to-useeffect/)