Why does useLazyLoadQuery() appear to be like blocking and can cause a re-render?

316 views Asked by At

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:

enter image description here

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

There are 2 answers

5
OlegWock On BEST ANSWER

This is because Relay uses Suspense. From tutorial you linked:

Relay uses Suspense to show a loading indicator until the data is available.

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 show fallback 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 use useLazyLoadQuery 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.

3
Xiduzo On

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

  1. Render the Newsfeed component (from App.tsx)
  2. console.log("HERE 0");
  3. React.Suspense until useLazyLoadQuery is ready, and suspense all other code in the Newsfeed component
  4. when useLazyLoadQuery has data it will trigger to re-render the Newsfeed component
  5. Re-render the Newsfeed component
  6. console.log("HERE 0");
  7. React.Suspense does not kick in because useLazyLoadQuery has data
  8. console.log("HERE 1", JSON.stringify(data));
  9. ... rest of the Newsfeed component