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
App
component andChild
component.
App
component has a state which decides whether to show theChild
component or not.
Child
component has acount
state.
Child
has a button to update thecount
.
- Both
App
andChild
has three types ofuseEffect
callbacks useEffect
with no dependenciesuseEffect
with empty dependenciesuseEffect
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 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
useEffect
with no dependecies is being executed.
- 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 theuseEffect
callbacks will be called.
- App: useEffect(() => {}, [showChild])
- App
useEffect
withshowChild
as dependecy is being executed. - This is getting called because this is the mount phase of the
App
component, and in mount phase all theuseEffect
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 - β 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
showChild
dependencies cleanup. - This cleanup happens because
showChild
is 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
Child
component, and in mount phase all theuseEffect
callbacks will be called.
- 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 theuseEffect
callbacks will be called.
- App: useEffect(() => {})
- App useEffect with no dependencies is being executed.
- 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 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
count
as dependency cleanup. - This is getting called because
count
has updated.
- Child: useEffect(() => {})
- Child useEffect with no dependencies is being executed.
- 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 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
count
as 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
showChild
as dependency clean up. - This is getting called because
showChild
has updated here.
- App: useEffect(() => {})
- App useEffect with no dependencies is getting executed
- 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 in
EpicReact.Dev
by Kent C. Dodds
- Understand the React Hook Flow in
The Beginners Guide To React
by 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.