I am following the tutorial of Relay and it is Step 3, as on https://relay.dev/docs/tutorial/queries-1/. The code with my debugging, is
export default function Newsfeed() {
console.log("HERE 0")
const data = useLazyLoadQuery(
NewsfeedQuery,
{}
)
console.log("HERE 1", JSON.stringify(data));
and in the Google Chrome’s dev console, I am able to see the following:
So it is very strange: it looks like it is able to reach the "HERE 0", and then fetch data, as if it is blocking I/O (network fetch) (the term blocking I/O means, like in the old days, when we do a Unix system call read()
, this function will never return until it has finished the reading task, unlike nowadays, we do a JavaScript fetch()
and set up a then
handler and fetch()
immediately returns without reading a single byte from the network), and then cause a re-render of the component again: note that it didn’t go to "HERE 1", but did a "HERE 0" again, and then "HERE 1".
This is very different from what React works like. Traditional React app is like,
const [data, setData] = useState(null);
useEffect(() => {
// fetching data here
}, []);
return (
<div>{
data && ... // display the data
}</div>
);
that is, it will go to the end of function and does not show anything, because data
is null. Once data
is something caused by the setData
, which causes a re-render, then the component will show something. So it is a running to the completion of the function TWICE.
The useLazyLoadQuery()
code above does not look like a "run to completion TWICE". It looks like it magically can cause a re-render in the middle.
In fact it doesn’t look like it is "lazy". It looks like it is diligently working (how the blocking I/O was in the old days).
How does it work?
2
Answers
This is because Relay uses Suspense. From tutorial you linked:
When using Suspense for data fetch (either throwing promises or using new
use
hook), React will stop rendering (‘suspend’) component if it requires some data which isn’t here yet. React will showfallback
of nearest Suspense boundary and re-render subtree when data is loaded.In your case
useLazyLoadQuery
requests some data under the hood and Next.js handles all Suspense-related stuff, so this might be less noticeable. But if you’d try to useuseLazyLoadQuery
in plain React project (i.e. without frameworks), you probably will get error from React as it was unable to find Suspense boundary, unless you explicitly define one.I did a bit of digging about all things Suspense and wrote an article some time ago. If you’re interested in how Suspense works it might be good starting point.
Digging a bit deeper into the relay documentation which states:
To render loading states while a query is being fetched, we rely on React Suspense. Suspense is a new feature in React that allows components to interrupt or "suspend" rendering in order to wait for some asynchronous resource (such as code, images or data) to be loaded; when a component "suspends", it indicates to React that the component isn’t "ready" to be rendered yet, and won’t be until the asynchronous resource it’s waiting for is loaded. When the resource finally loads, React will try to render the component again.
Looking at the repo from the tutorial you are following I can see the app is wrapped in a
<React.Suspense fallback={<LoadingSpinner />}>
This means all code below your
useLazyLoadQuery
won’t be executed until it is giving the signal the component is ready.All code above will get executed which is why you are seeing
"HERE 0"
twice.The rendering process will be as follows
Newsfeed
component (fromApp.tsx
)console.log("HERE 0");
React.Suspense
untiluseLazyLoadQuery
is ready, and suspense all other code in theNewsfeed
componentuseLazyLoadQuery
has data it will trigger to re-render theNewsfeed
componentNewsfeed
componentconsole.log("HERE 0");
React.Suspense
does not kick in becauseuseLazyLoadQuery
has dataconsole.log("HERE 1", JSON.stringify(data));
Newsfeed
component