The Lifecycle of React Hooks Component
Tags: react, javascript, reacthooks
Published: 2020-11-13 08:06:36

## Hello World 👋

In this article, we will see the order in which different `useEffect` callbacks and cleanups happen. We will also see how it differs when the app mounts, unmounts, updates.

<!-- ![hook-flow.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1604974451424/cLhJts36-.png) -->

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

> This image is taken from https://github.com/donavon/hook-flow.

> I took the example shown in this article from `React Hooks` workshop in [EpicReact.Dev](https://epicreact.dev) by [Kent C. Dodds](https://kentcdodds.com).


> I have added relevant links at the end of this article. Check those out for more detailed video explanations given by [Kent C. Dodds](https://kentcdodds.com). 

Every component has three phases:
1. Mount
1. Update
1. Unmount

### Mount - Hooks Flow
This stage is when the component initially mounts on a page.
In this stage, the flow of hooks is as follows:
1. Run lazy initializers
    - Lazy initializers are functions that we pass to `useState` and `useReducer`. Those functions will be run only in this mount stage.
1. Render
    - This is where all the `useState` hooks and other things are present.
1. React updates DOM
    - Updating of DOM is not same as the browser painting the screen.
1. Run Layout Effects
    - We will see layout effects in future articles.
1. Browser paints the screen
1. Run Effects

### Update - Hooks Flow
This stage is when the component updates. 
An update can happen for all the following reasons:
- Parent of the component re-renders
- State of the component changes
- Context changes

In this stage, the flow of hooks is as follows:
- Render
- React updates DOM
- Cleanup Layout Effects
    - (Like `useEffect`) `useLayoutEffect` also has a cleanup phase.
- Run Layout Effects
- Browser paints the screen
- Cleanup Effects
- Run Effects

As you can see, this is similar to what we saw for the mount stage, except that this also has `Cleanup Layout Effects` and `Cleanup Effects`.

### Unmount - Hooks Flow
This stage is when the component unmounts from a page.

In this stage, the flow of hooks is as follows:
- Cleanup Layout Effects
- Cleanup Effects

Only cleanups will be run in this stage.


### Types of useEffect callbacks
Before we see an example, let's take a look at 3 different types of `useEffect` callbacks.
1. useEffect with no dependencies
1. useEffect with empty dependencies
1. useEffect with some dependencies

#### 1. useEffect with no dependencies
```js
useEffect(() => {
    console.log('useEffect(() => {})') // Line 1
    return () => {
        console.log('useEffect(() => {}) cleanup') // Line 2
    }
})
```

This `useEffect` callback has no dependencies. 

- Callback function(Line 1) will be run when:
    - Component is mounted
    - Component is updated
- Cleanup function(Line 2) will be run when:
    - Component is updated
    - Component is unmounted

#### 2. useEffect with empty dependencies
```js
useEffect(() => {
    console.log('useEffect(() => {}, [])') // Line 1
    return () => {
        console.log('useEffect(() => {}, []) cleanup') // Line 2
    }
}, [])
```

This `useEffect` callback has empty dependencies. 

- Callback function(Line 1) will be run when:
    - Component is mounted
- Cleanup function(Line 2) will be run when:
    - Component is unmounted

**Note:** This `useEffect` callback will not be executed when the component updates because of the empty dependency array.


#### 3. useEffect with some dependencies
```js
useEffect(() => {
    console.log('useEffect(() => {}, [count])') // Line 1
    return () => {
        console.log('useEffect(() => {}, [count]) cleanup') // Line 2
    }
}, [count])
```

This `useEffect` callback has one or more dependencies. 

- Callback function(Line 1) will be run when:
    - Component is mounted
    - Any of the dependency is changed - In this case when the count is changed.
- Cleanup function(Line 2) will be run when:
    - Any of the dependency is changed - In this case when the count is changed.
    - Component is unmounted

## Example
Consider the below example

```js
import React from "react";

function App() {
  console.log("App: render start");

  const [showChild, setShowChild] = React.useState(() => {
    console.log("App: useState(() => false)");
    return false;
  });

  console.log(`App: showChild = ${showChild}`);

  React.useEffect(() => {
    console.log("App: useEffect(() => {})");
    return () => {
      console.log("App: useEffect(() => {}) cleanup");
    };
  });

  React.useEffect(() => {
    console.log("App: useEffect(() => {}, [])");
    return () => {
      console.log("App: useEffect(() => {}, []) cleanup");
    };
  }, []);

  React.useEffect(() => {
    console.log("App: useEffect(() => {}, [showChild])");
    return () => {
      console.log("App: useEffect(() => {}, [showChild]) cleanup");
    };
  }, [showChild]);

  const element = (
    <>
      <label>
        <input
          type="checkbox"
          checked={showChild}
          onChange={(e) => setShowChild(e.target.checked)}
        />{" "}
        show child
      </label>
      <div>
        {showChild ? <Child /> : null}
      </div>
    </>
  );

  console.log("App: render end");

  return element;
}
```

```js
import React from "react";

function Child() {
  console.log("    Child: render start");

  const [count, setCount] = React.useState(() => {
    console.log("    Child: useState(() => 0)");
    return 0;
  });

  console.log(`    Child: count = ${count}`);

  React.useEffect(() => {
    console.log("    Child: useEffect(() => {})");
    return () => {
      console.log("    Child: useEffect(() => {}) cleanup");
    };
  });

  React.useEffect(() => {
    console.log("    Child: useEffect(() => {}, [])");
    return () => {
      console.log("    Child: useEffect(() => {}, []) cleanup");
    };
  }, []);

  React.useEffect(() => {
    console.log("    Child: useEffect(() => {}, [count])");
    return () => {
      console.log("    Child: useEffect(() => {}, [count]) cleanup");
    };
  }, [count]);

  const element = (
    <button onClick={() => setCount((previousCount) => previousCount + 1)}>
      {count}
    </button>
  );

  console.log("    Child: render end");

  return element;
}
```
{% codesandbox kentcdodds-hooks-flow-dc9w0 %}

- we have an `App` component and `Child` component.
- `App` component has a state which decides whether to show the `Child` component or not.
- `Child` component has a `count` state.
- `Child` has a button to update the `count`.
- Both `App` and `Child` has three types of `useEffect` callbacks
    - `useEffect` with no dependencies
    - `useEffect` with empty dependencies
    - `useEffect` with one or more dependencies.

We will see how the flow looks like for each of the following steps:
1. App is mounted
1. Child is mounted by updating the state in App
1. Child is updated by updating the count in Child
1. Child is unmounted by updating the state in App

### 1. App is mounted
Here the `App` is in mount phase, so from the diagram, the order should be
1. ✅ Run lazy initializers of App
1. ✅ Render of App
1. ✅ React updates DOM of App
1. ❌ Cleanup layout effects of App
1. ✅ Run Layout Effects of App
1. ✅ Browser paints screen of App
1. ❌ Cleanup Effects of App
1. ✅ Run Effects of App

When the `App` is mounted, we see the following console logs.

1. App: render start 
    - App rendering starts.
1. App: useState(() => false) 
    - App lazy initializer is getting executed.
1. App: showChild = false 
    - App renders.
1. App: render end 
    - App rendering finishes.
1. App: useEffect(() => {}) 
    - App `useEffect` with no dependecies is being executed.
1. App: useEffect(() => {}, []) 
    - App `useEffect` with empty dependecies is being executed.
    - This is getting called because this is the mount phase of the `App` component, and in mount phase all the `useEffect` callbacks will be called.
1. App: useEffect(() => {}, [showChild]) 
    - App `useEffect` with `showChild` as dependecy is being executed.
    - This is getting called because this is the mount phase of the `App` component, and in mount phase all the `useEffect` callbacks will be called.

**Notes:**
- All the `useEffect` callbacks will get executed on the initial mount of the component
- `useEffect` callbacks will be run in the order in which they appear.


### 2. Child is mounted by updating the state in App
Let's click on `show child` checkbox. This will mount the `Child` component.

Here `Child` will be in the mount phase and `App` will be in the update phase.

As per diagram, the order for `Child` will be
1. ✅ Run lazy initializers of Child
1. ✅ Render of Child
1. ✅ React updates DOM of Child
1. ❌ Cleanup layout effects of Child
1. ✅ Run Layout Effects of Child
1. ✅ Browser paints screen of Child
1. ❌ Cleanup Effects of Child
1. ✅ Run Effects of Child

And for `App`, 
1. ❌ Run lazy initializers of App
1. ✅ Render of App
1. ✅ React updates DOM of App
1. ✅ Cleanup layout effects of App
1. ✅ Run Layout Effects of App
1. ✅ Browser paints screen of App
1. ✅ Cleanup Effects of App
1. ✅ Run Effects of App

We will see the following console logs.

1. App: render start 
    - App rendering starts.
    - Lazy initializer will **NOT** be run now. It runs only on the initial mount.
1. App: showChild = true 
    - App renders.
1. App: render end
    - App rendering finishes.
1. Child: render start 
    - Child is mounted and Child starts getting rendered.
1. Child: useState(() => 0) 
    -  Child lazy initializer is getting executed since this is the mount phase of Child.
1. Child: count = 0 
    - Child renders.
1. Child: render end .
    - Child rendering finishes.
1. App: useEffect(() => {}) cleanup 
    - App useEffect with no dependencies cleanup.
1. App: useEffect(() => {}, [showChild]) cleanup 
    - App useEffect with `showChild` dependencies cleanup.
    - This cleanup happens because `showChild` is getting updated here.
1. Child: useEffect(() => {}) 
    - Child useEffect with no dependencies is being executed.
1. Child: useEffect(() => {}, []) 
    - Child useEffect with empty dependencies is being executed.
    - This is getting called because this is the mount phase of the `Child` component, and in mount phase all the `useEffect` callbacks will be called.
1. Child: useEffect(() => {}, [count]) 
    - Child useEffect with `count` as dependency is being executed.
    - This is getting called because this is the mount phase of the `Child` component, and in mount phase all the `useEffect` callbacks will be called.
1. App: useEffect(() => {}) 
    - App useEffect with no dependencies is being executed.
1. App: useEffect(() => {}, [showChild]) 
    - App useEffect with `showChild` dependencies is being executed.
    - This is getting called because `showChild` has updated.

**Notes:**
- While rendering the `App` component, we have `<Child />` in its markup. But you can see the `Child` render starts after the `App` render ends.
- This is because `<Child />` is not same as calling calling `Child` function. It's basically calling `React.createElement(Child)`. 
- React will only start calling `Child` when it's time for rendering it.

### 3. Child is updated by updating the count in Child
Let's click on the `count` button to update the `count` present in `Child`.

Here `Child` will be in the update phase and `App` has no change.

As per diagram, the order for `Child` will be
1. ❌ Run lazy initializers of Child
1. ✅ Render of Child
1. ✅ React updates DOM of Child
1. ✅ Cleanup layout effects of Child
1. ✅ Run Layout Effects of Child
1. ✅ Browser paints screen of Child
1. ✅ Cleanup Effects of Child
1. ✅ Run Effects of Child

We will see the following console logs
1. Child: render start 
    - Child rendering starts.
1. Child: count = 1 
    - Child renders
1. Child: render end 
    - Child rendering ends.
1. Child: useEffect(() => {}) cleanup 
    - Child useEffect with no dependencies cleanup.
1. Child: useEffect(() => {}, [count]) cleanup 
    - Child useEffect with `count` as dependency cleanup.
    - This is getting called because `count` has updated. 
1. Child: useEffect(() => {}) 
    - Child useEffect with no dependencies is being executed.
1. Child: useEffect(() => {}, [count]) 
    - Child useEffect with `count` as dependency is being executed.
    - This is getting called because `count` has updated.

#### 4. Child is unmounted by updating the state in App
Let's click on the `show child` checkbox to unmount the `Child` component.

Here `Child` will be in unmount phase and `App` will be in update phase

As per diagram, the order for `Child` will be
1. ❌ Run lazy initializers of Child
1. ❌ Render of Child
1. ❌ React updates DOM of Child
1. ✅ Cleanup layout effects of Child
1. ❌ Run Layout Effects of Child
1. ❌ Browser paints screen of Child
1. ✅ Cleanup Effects of Child
1. ❌ Run Effects of Child

And for App,
1. ❌ Run lazy initializers of App
1. ✅ Render of App
1. ✅ React updates DOM of App
1. ✅ Cleanup layout effects of App
1. ✅ Run Layout Effects of App
1. ✅ Browser paints screen of App
1. ✅ Cleanup Effects of App
1. ✅ Run Effects of App

We will see the following console logs
1. App: render start
    - App rendering starts.
1. App: showChild = false 
    - App renders
1. App: render end 
    - App rendering ends
1. Child: useEffect(() => {}) cleanup 
    - Child useEffect with no dependencies cleanup
1. Child: useEffect(() => {}, []) cleanup 
    - Child useEffect with empty dependencies cleanup
    - This is getting called here because this in unmount phase and in unmount phase all the cleanups will be called.
1. Child: useEffect(() => {}, [count]) cleanup 
    - Child useEffect with `count` as dependency cleanup
    - This is getting called here because this in unmount phase and in unmount phase all the cleanups will be called.
1. App: useEffect(() => {}) cleanup 
    - App useEffect with no dependencies clean up
1. App: useEffect(() => {}, [showChild]) cleanup 
    - App useEffect with `showChild` as dependency clean up.
    - This is getting called because `showChild` has updated here.
1. App: useEffect(() => {}) 
    - App useEffect with no dependencies is getting executed
1. App: useEffect(() => {}, [showChild]) 
    - App useEffect with `showChild` as dependency is getting executed
    - This is getting called because `showChild` has updated here.


And finally, when the `App` component also unmounts, the `cleanup` of all the `App` `useEffect`s will be called.

### Links and References:
- [Hooks Flow](https://epicreact.dev/modules/react-hooks/hooks-flow) in `EpicReact.Dev` by [Kent C. Dodds](https://kentcdodds.com)
- [Understand the React Hook Flow](https://egghead.io/lessons/react-understand-the-react-hook-flow) in `The Beginners Guide To React` by [Kent C. Dodds](https://kentcdodds.com)
- [Hook Flow Diagram](https://github.com/donavon/hook-flow)

## What's Next?
In the next article, we will look at what lifting state and colocating state mean in React. And also we will see when they will be useful.

#### Until Next Time 👋

If you liked this article, check out
- [React Hooks: Managing State With useState Hook](https://blog.bhanuteja.dev/react-hooks-managing-state-with-usestate-hook)
- [How to Create a Reusable LocalStorage Hook](https://blog.bhanuteja.dev/how-to-create-a-reusable-localstorage-hook)
- [Easily Detect Outside Click Using useRef Hook](https://blog.bhanuteja.dev/easily-detect-outside-click-using-useref-hook)


If you have any comments, please leave them below or you can also @ me on Twitter ([@pbteja1998](https://twitter.com/pbteja1998)), or feel free to follow me.