2

i am using useEffect inside a custom hook called useCustomHook, i am using this useCustomHook in two component i.e (First,Second),butuseEffect getting called only when the First and Second component get's rendered.

For example

I have a First component

import React,{useState} from 'react'
import useCustomHook from './customHook'

function First(){
 console.log("component First rendering")
 const [count,setCount]=useState(0)
 useCustomHook(count)

 return (<div>First component</div>)

}

This is my Second component

import React,{useState} from 'react'
import useCustomHook from './customHook'

function Second(){
 console.log("component Second rendering")
 const [count,setCount]=useState(0)
 useCustomHook(count)

 return (<div>Second component</div>)

}

And this is my customHook

import {useEffect} from 'react'

function useCustomHook(count){
  console.log("useCustomHook getting called")
  useEffect(()=>{
 console.log("useEffect gets called") //this function is running after both component rendered
  },[count])

}

My main App component

import First from './first'
import Second from './second'

function App(){
   return (
      <div>
        <First/>
        <Second/>
      </div>
    )
}

My console output is :

1) component First rendering

2) useCustomHook getting called

3) component Second rendering

4) useCustomHook getting called

5) (2) useEffect gets called

I want to know

Why is the line 5 output is not after the line 2, Why is Second component log is happening after line 2, because useEffect should be called after the useCustomHook being called by the First component but before that Second component log is called. why is useEffect inside useCustomHook not getting called before Second component log.

user8989
  • 580
  • 2
  • 6
  • 21

2 Answers2

4

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
Yousaf
  • 27,861
  • 6
  • 44
  • 69
  • What is the difference b/w component mounting, component rendering, and browser painting the screen. Could you please explain. – user8989 Jun 12 '20 at 16:56
  • see [React Mount vs Render](https://reacttraining.com/blog/mount-vs-render/#:~:text=Here's%20a%20brief%20tldr%3B,initial%20DOM%20from%20those%20instructions.) and [Browser Painting](https://css-tricks.com/browser-painting-and-considerations-for-web-performance/) – Yousaf Jun 12 '20 at 16:59
  • Thnks man,could you please help me with this question,it will be a great help https://stackoverflow.com/questions/62285513/how-does-css-loader-resolve-resources-in-webpack/62287278#62287278 – user8989 Jun 12 '20 at 17:08
1

The order that you have mentioned perfectly makes sense and thats how hooks works.

Flow:

  • First component begins execution.
  • In First component, after useCustomHook(count) line of code, the useCustomHook will be executed
  • In useCustomHook the console.log is printed and the useEffect is executed and the CALLBACK that use effect takes is REGISTERED and NOT executed.
  • First components returns JSX. i.e. component is mounted/rendered.
  • Once First component is mounted, then the useEffect's callback in useCustomHook is called.
  • Basically the useCustomHook inside the First component is scoped to the component.

Same goes with Second component as well...

gdh
  • 13,114
  • 2
  • 16
  • 28
  • 2
    But the First component should be rendered first and the useEffect callback should be called after that, but why is the Second component is getting called before that the useEffect callback by First component – user8989 Jun 11 '20 at 15:15