Your output is as it should be.
I think you are confused about the output because you think that useEffect
is same as componentDidMount
but that is not correct. They both are different, couple of important differences between them are mentioned below:
They run at different times
(related to your question)
They both are called after the initial render of the component but useEffect
is called after the browser has painted the screen whereas componentDidMount
is called before the screen is painted by the browser.
Capturing props and state
(not related to your question, feel free to skip to the end of the answer)
useEffect
captures the state and props whereas componentDidMount
doesn't do this.
Consider the following code snippets to understand what useEffect captures the state and props means.
class App extends React.Component {
constructor() {
super();
this.state = {
count: 0
};
}
componentDidMount() {
setTimeout(() => {
console.log('count value = ' + this.state.count);
}, 4000);
}
render() {
return (
<div>
<p>You clicked the button { this.state.count } times</p>
<button
onClick={ () => this.setState(prev => ({ count: prev.count + 1 })) }>
Increment Counter
</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
function App() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
setTimeout(() => {
console.log('count value = ' + count);
}, 4000);
}, [])
return (
<div>
<p>You clicked the button { count } times</p>
<button
onClick={ () => setCount(count + 1) }>
Increment Counter
</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Both code snippets are same except first one has class based component and second one has functional component.
These both snippets have a variable named count
in the state and they both log the value of the count
variable to the console after 4 seconds. They also include a button which can be used to increment the value of the count
.
Try to click the button (4 or 5 times) before the value of the count
is logged on the console.
If you thought that componentDidMount
and useEffect
are same then you might be surprised to see that both code snippets log different values of count
variable after 4 seconds.
Class based code snippet logs the latest value whereas functional component based code snippet logs the initial value of count
variable.
Reason why they log different value of count
variable is because:
this.state
inside class component always points to latest state, so it logs the latest value of count
after 4 seconds.
useEffect
captures the initial value of the count
variable and logs the captured value instead of the latest value.
For in-depth explanation of the differences between useEffect
and componentDidMount
, i suggest you read following articles
Coming back to your question
If you paid attention to the first part of my answer that is related to your question, you probably now understand why useEffect
runs its callback after both First
and Second
components have mounted.
If not, then let me explain.
After the execution of useCustomHook
function that was called from within the First
component, First
component is mounted and if it was a class based component, its componentDidMount
lifecycle function would have been called at this point.
After First
component has mounted, Second
component mounts and if this too was a class based component, its componentDidMount
lifecycle function would have been called at this point.
After both components have mounted, browser paints the screen and as a result, you see the output on the screen. After the browser has painted the screen, callback function of the useEffect gets executed for both First
and Second
component.
In short, useEffect
lets the browser paint the screen before running its effect/callback. That is why useEffect gets called
is logged at at the end of the output.
You can see more details about this on official docs: Timing of effects
If you turn First
and Second
component in to class components, then output will be as:
1. component First rendering
2. component Second rendering
3. component First mounted. // console.log statement inside componentDidMount
4. component Second mounted. // console.log statement inside componentDidMount
You might expect 3rd line to be at 2nd place and 2nd line at 3rd place but that's not the case because react first executes the render functions of all the child components before they are inserted in the DOM and only after they are inserted in the DOM, componentDidMount
of each component executes.
If you create Third
and Fourth
components and create following hierarchy of class components:
App
|__ First
| |__ Third
| |__ Fourth
|
|__ Second
then you will se the following output:
1. First component constructor
2. component First rendering
3. Third component constructor
4. component Third rendering
5. Fourth component constructor
6. component Fourth rendering
7. Second component constructor
8. component Second rendering
9. component Fourth mounted
10. component Third mounted
11. component First mounted
12. component Second mounted