Search code examples
reactjsrelayjsreact-suspensereact-relay

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


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?


Solution

  • 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.