Table of Contents
- Mount - Hooks Flow
- Update - Hooks Flow
- Unmount - Hooks Flow
- Types of useEffect callbacks
- 1. useEffect with no dependencies
- 2. useEffect with empty dependencies
- 3. useEffect with some dependencies
- Example
- 1. App is mounted
- 2. Child is mounted by updating the state in App
- 3. Child is updated by updating the count in Child
- 4. Child is unmounted by updating the state in App
- Links and References:
- Whatβs Next?
- Until Next Time π
Related Posts
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.
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 by Kent C. Dodds.
I have added relevant links at the end of this article. Check those out for more detailed video explanations given by Kent C. Dodds.
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 EffectsUpdate - 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 EffectsAs 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 dependencies1. useEffect with no dependencies
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
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
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
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;
}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;
}- we have an
Appcomponent andChildcomponent.
Appcomponent has a state which decides whether to show theChildcomponent or not.
Childcomponent has acountstate.
Childhas a button to update thecount.
- Both
AppandChildhas three types ofuseEffectcallbacks useEffectwith no dependenciesuseEffectwith empty dependenciesuseEffectwith 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 AppWhen the
App is mounted, we see the following console logs.- App: render start
- App rendering starts.
- App: useState(() => false)
- App lazy initializer is getting executed.
- App: showChild = false
- App renders.
- App: render end
- App rendering finishes.
- App: useEffect(() => {})
- App
useEffectwith no dependecies is being executed.
- App: useEffect(() => {}, [])
- App
useEffectwith empty dependecies is being executed. - This is getting called because this is the mount phase of the
Appcomponent, and in mount phase all theuseEffectcallbacks will be called.
- App: useEffect(() => {}, [showChild])
- App
useEffectwithshowChildas dependecy is being executed. - This is getting called because this is the mount phase of the
Appcomponent, and in mount phase all theuseEffectcallbacks 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 - β Run lazy initializers of Child
- β Render of Child
- β React updates DOM of Child
- β Cleanup layout effects of Child
- β Run Layout Effects of Child
- β Browser paints screen of Child
- β Cleanup Effects of Child
- β Run Effects of Child
And for
App, - β Run lazy initializers of App
- β Render of App
- β React updates DOM of App
- β Cleanup layout effects of App
- β Run Layout Effects of App
- β Browser paints screen of App
- β Cleanup Effects of App
- β Run Effects of App
We will see the following console logs.
- App: render start
- App rendering starts.
- Lazy initializer will NOT be run now. It runs only on the initial mount.
- App: showChild = true
- App renders.
- App: render end
- App rendering finishes.
- Child: render start
- Child is mounted and Child starts getting rendered.
- Child: useState(() => 0)
- Child lazy initializer is getting executed since this is the mount phase of Child.
- Child: count = 0
- Child renders.
- Child: render end .
- Child rendering finishes.
- App: useEffect(() => {}) cleanup
- App useEffect with no dependencies cleanup.
- App: useEffect(() => {}, [showChild]) cleanup
- App useEffect with
showChilddependencies cleanup. - This cleanup happens because
showChildis getting updated here.
- Child: useEffect(() => {})
- Child useEffect with no dependencies is being executed.
- Child: useEffect(() => {}, [])
- Child useEffect with empty dependencies is being executed.
- This is getting called because this is the mount phase of the
Childcomponent, and in mount phase all theuseEffectcallbacks will be called.
- Child: useEffect(() => {}, [count])
- Child useEffect with
countas dependency is being executed. - This is getting called because this is the mount phase of the
Childcomponent, and in mount phase all theuseEffectcallbacks will be called.
- App: useEffect(() => {})
- App useEffect with no dependencies is being executed.
- App: useEffect(() => {}, [showChild])
- App useEffect with
showChilddependencies is being executed. - This is getting called because
showChildhas 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 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 - β Run lazy initializers of Child
- β Render of Child
- β React updates DOM of Child
- β Cleanup layout effects of Child
- β Run Layout Effects of Child
- β Browser paints screen of Child
- β Cleanup Effects of Child
- β Run Effects of Child
We will see the following console logs
- Child: render start
- Child rendering starts.
- Child: count = 1
- Child renders
- Child: render end
- Child rendering ends.
- Child: useEffect(() => {}) cleanup
- Child useEffect with no dependencies cleanup.
- Child: useEffect(() => {}, [count]) cleanup
- Child useEffect with
countas dependency cleanup. - This is getting called because
counthas updated.
- Child: useEffect(() => {})
- Child useEffect with no dependencies is being executed.
- Child: useEffect(() => {}, [count])
- Child useEffect with
countas dependency is being executed. - This is getting called because
counthas 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 phaseAs per diagram, the order for
Child will be - β Run lazy initializers of Child
- β Render of Child
- β React updates DOM of Child
- β Cleanup layout effects of Child
- β Run Layout Effects of Child
- β Browser paints screen of Child
- β Cleanup Effects of Child
- β Run Effects of Child
And for App,
- β Run lazy initializers of App
- β Render of App
- β React updates DOM of App
- β Cleanup layout effects of App
- β Run Layout Effects of App
- β Browser paints screen of App
- β Cleanup Effects of App
- β Run Effects of App
We will see the following console logs
- App: render start
- App rendering starts.
- App: showChild = false
- App renders
- App: render end
- App rendering ends
- Child: useEffect(() => {}) cleanup
- Child useEffect with no dependencies cleanup
- 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.
- Child: useEffect(() => {}, [count]) cleanup
- Child useEffect with
countas dependency cleanup - This is getting called here because this in unmount phase and in unmount phase all the cleanups will be called.
- App: useEffect(() => {}) cleanup
- App useEffect with no dependencies clean up
- App: useEffect(() => {}, [showChild]) cleanup
- App useEffect with
showChildas dependency clean up. - This is getting called because
showChildhas updated here.
- App: useEffect(() => {})
- App useEffect with no dependencies is getting executed
- App: useEffect(() => {}, [showChild])
- App useEffect with
showChildas dependency is getting executed - This is getting called because
showChildhas updated here.
And finally, when the
App component also unmounts, the cleanup of all the App useEffects will be called.Links and References:
- Hooks Flow in
EpicReact.Devby Kent C. Dodds
- Understand the React Hook Flow in
The Beginners Guide To Reactby Kent C. Dodds
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

Written by
24yo developer and blogger. I quit my software dev job to make it as an independent maker. I write about bootsrapping Feather.
