On the screen I get : Hello { "name" : "Jack" }
In console I get:
- App rendering
- useFetch useEffect
- 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 :
- 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 :
- 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 )
- 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 }
}
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
:
(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(...)
)
(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)