reactjsreact-queryreact-suspense

react query suspense Issue (suspended while responding to synchronous input)


"@tanstack/react-query": "5.8.3",
"react-router-dom": "6.18.0",

I think this suspense logic will be works normally but it wasn't

code: https://codesandbox.io/s/stoic-easley-dh2ds9

Page 1: This is an example of data fetching using the general useQuery function. It works normally.

Page 2: This is an example of using the useSuspenseQuery to use React Suspense. The fallback of Suspense does not work and results in the error below:

Error A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition.

if a suspension boundary is set on the router above this page, the fallback works well.

However, I want it to work like the example below, but handle suspension at the page level rather than the component level.

https://codesandbox.io/s/lgt8ly


Solution

  • In your code you have Suspense boundary inside the component which caused suspension. It won't work since when component suspends, React will walk up the tree until it finds <Suspense>. Any suspense boundaries inside the component won't be even considered, at the moment of suspension React might even not know there are any boundaries inside the component.

    To handle suspension at page level you'll need a wrapper component like this:

    import { useSuspenseQuery } from "@tanstack/react-query";
    import { Suspense } from "react";
    
    export const Page2 = () => {
      return (
        <Suspense fallback={"loading!"}>
          <Page2Inner />
        </Suspense>
      );
    };
    
    const Page2Inner = () => {
      const { status, data, error } = useSuspenseQuery({
        queryKey: ["sus_post"],
        queryFn: async () => {
          await new Promise((resolve) => setTimeout(resolve, 3000));
          const response = await fetch(
            `https://jsonplaceholder.typicode.com/posts/2`
          );
          return response.json();
        }
      });
    
      return (
        <div>
          <h2>this is page2</h2>
          {JSON.stringify(data.title)}
        </div>
      );
    };
    

    To avoid making wrapper component for every page you can create a HOC for this.

    const withSuspense = (Component) => (props) => {
      return (
        <Suspense fallback={"loading!"}>
          <Component {...props} />
        </Suspense>
      );
    };
    
    export const Page2 = withSuspense(Page2Inner);