Home > Back-end >  Why runs the component 2 times with useEffect?
Why runs the component 2 times with useEffect?

Time:06-08

On the screen I get : Hello { "name" : "Jack" }

In console I get:

  1. App rendering
  2. useFetch useEffect
  3. App rendering

Because what I have seen in the console , I think App.js must have ran 2 times and useFetch must have ran 1 time .

I think this is behind :

  1. App.js calls useFetch and writes on console that App rendering . App.js does NOT wait useFetch to complete, so Hello undefined is on the screen . BUT we don't see undefined on the screen , because it is so fast and it goes away -> because of 2. step :
  2. While App.js writes on console and displays undefined on screen , useFetch is running . When useFetch completes its running it gives back the correct object ( Jack )
  3. App.js component runs again and writes again on console and displays the correct object ( Hello Jack )

But if this is true , then why runs App.js 2 times ?

Somebody said that my theory about this is false and useFetch gives back 2 times value . First it gives back null as object and second time the correct value , which is from fetch . So he said that useFetch does not wait fetch function to complete , so it gives back first time null and when fetch function completes then it gives back Jack . But if he has right , then why is that ? Why does not wait useFetch to complete its functions to complete ?

Can somebody write step by step whats happening behind ?

App.js :

import { useFetch } from './useFetch';

function App() {
    const { data } = useFetch({ url : "jack.json" })
    console.log('App rendering')
    return (
        <div className="App">
        <div>Hello</div>
        <div>{JSON.stringify(data)}</div> )
}

export default App;

useFetch.js :

import { useState, useEffect } from "react";

export const useFetch = (options) => {
    const [data, setData] = useState(null);
    useEffect(() => {
        console.log("useFetch useEffect");
        fetch(options.url)
            .then( response => response.json())
            .then( json  => setData(json))
    }, [])

    return { data }
}

My Question inspired this : enter image description here


From the start, it's just like calling a regular function...

If you refer to the diagram above, we are at the stage called render, because the body of a functional component is the equivalent of the render function in class components.

First, useFetch is called

const { data } = useFetch({ url : "jack.json" });

Inside useFetch

const [data, setData] = useState(null);

At this point, data is null

Next useEffect is called

useEffect(() => {
    console.log("useFetch useEffect");
    fetch(options.url)
        .then( response => response.json())
        .then( json  => setData(json))
}, []);

useEffect creates an effect watcher. This is basically a function which is invoked every time one of its dependencies changes. However, it is not invoked until after the first time the component renders.

If you refer to the diagram above, the watcher will only be invoked for the first time at the point called componentDidMount.

Back inside App:

enter image description here (image reposted to avoid scrolling)

console.log('App rendering')

We get a nice console.log output...

return (
        <div className="App">
        <div>Hello</div>
        <div>{JSON.stringify(data)}</div>
);

Here we return control back to react, and it will now attempt to take our html and mount it inside the dom.

Note that data is still null. Every other component react encounters will go through the same process described above.

So far, the steps leading up to this point can be summarized as Mounting (See the diagram above), and we have now reached the componentDidMount stage.

componentDidMount

React will now invoke our effect.

Note: If you had more than one effect, react will invoke all of them, and none will be skipped for this first round of effectual work that react does. Also they will all be invoked in the same order you declared them in.

useEffect watcher invoked

console.log("useFetch useEffect");

Another nice printout to console.log

fetch(options.url)
    .then( response => response.json())
    .then( json  => setData(json))

fetch is invoked, but since we are not allowed to wait for promises inside effects, the fetch just runs.

Note: If your effect returns any function, react saves it to run it the next time an update/unmount occurs.


At this point, we just kinda idle until something interesting hap...oh wait fetch is done.

State Updates (setData(...))

enter image description here (image reposted to avoid scrolling)

.then( json  => setData(json))

setData will update the state of the component. If you refer to the diagram above once again, you'll see that when a state update occurs, the next step is to render.

Therefore, react will once again repeat the same steps as above, but it does not re-create the useEffect, or the useState again, because it has stored them from the previous render.

Sidenote: This is also how react is able to detect when you are calling a hook (useEffect, useState, etc) conditionally. If react detects a new hook after the first render is finished, it will warn you of this

App is rendered again

console.log('App rendering')

Next...

return (
        <div className="App">
        <div>Hello</div>
        <div>{JSON.stringify(data)}</div>
);

And we once again return the html content of our component (this time data is now whatever was sent to setData).

Updating

As you can tell, we are now in the Updating Phase. From now on, react will simply wait for props/state updates, and runs any effects that depend on them in the componentDidUpdate stage of the lifecycle

Sidenote: Your useEffect should depend on options.url so that it is re-run when the url changes

useEffect(() => {
    console.log("useFetch useEffect");
    fetch(options.url)
        .then( response => response.json())
        .then( json  => setData(json))
}, [options?.url]);

Unmounting

If by some bad luck, your component is unmounted either by the parent, or by some strange magic, your component will be moved into the Unmounting phase. Once again, refer to the diagram above

In this phase, none of the effects are run. Instead, any functions returned by your "effect watcher", will be invoked to do cleanup.

Note: All the cleanup functions will be run in the order they were encountered (typically top-down)

  • Related