Reputation: 151
On the screen I get : Hello { "name" : "Jack" }
In console I get:
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 :
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 : https://youtu.be/dH6i3GurZW8?t=316
Upvotes: 1
Views: 237
Reputation: 43206
Here is a step-by-step of what's happening:
function App() {
const { data } = useFetch({ url : "jack.json" })
console.log('App rendering')
return (
<div className="App">
<div>Hello</div>
<div>{JSON.stringify(data)}</div> )
}
Before I begin, it may help to also understand how the react component lifecycle works:
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.
useFetch
is calledconst { data } = useFetch({ url : "jack.json" });
useFetch
const [data, setData] = useState(null);
At this point, data
is null
useEffect
is calleduseEffect(() => {
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
.
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.
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.
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
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
).
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]);
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)
Upvotes: 2
Reputation: 108
Your thinking is correct.
But if this is true , then why runs App.js 2 times ?
Because App
is a react component and like all other react components it will re-render whenever its data dependencies change. In your case, App
will re-render whenever the value of data
changes.
Your custom useFetch
hook uses the useEffect
hook to fetch data. Effects in React are performed after react components have rendered. That's why the console logs the first "App rendering". Once the useFetch
hook calls setData
, the value of data
is updated which triggers the second re-render of App
.
Upvotes: 3