I am using useSuspenseQuery
from React Query to fetch data in a React app. While the API is fetching, I want to show a loading state using <Suspense fallback="Loading" />
.
The issue occurs when I include a Header
component that updates its state with setTimeout
. In this case, the Body
component, which contains the query, renders twice instead of once. However, if I remove Header
, the Body
component renders only once, as expected.
Header
, Body
renders only once.setTimeout
duration (e.g., from 250ms
to 1000ms
), Body
also renders only once.setTimeout
duration is close to the API fetch duration.Header
and Body
are sibling components, not a parent-child relationship. I don't understand why Body
re-renders just because Header
updates its state.Header
affects this behavior.App.tsx
import { Suspense } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import Header from "./components/Header";
import Body from "./components/Body";
export default function App() {
const queryClient = new QueryClient();
return (
<QueryClientProvider client={queryClient}>
<Header />
<Suspense fallback={<div>Loading</div>}>
<Body />
</Suspense>
</QueryClientProvider>
);
}
Header.tsx
import React, { useEffect, useState } from "react";
const Header =() => {
const [change, setChange] = useState(false);
console.log("Header chang", change);
useEffect(() => {
setTimeout(() => {
setChange(true);
}, 250);
}, []);
return <h1 style={{ color: change ? "blue" : "red" }}>Header</h1>;
};
export default Header;
Body.tsx
import { useSuspenseQuery } from '@tanstack/react-query';
let count = 0;
const Body = () => {
const { data } = useSuspenseQuery({
queryKey: ['body'],
queryFn: async () => {
//api delay
await new Promise((r) => setTimeout(r, 200));
return {'message':'success'}
},
});
console.log('Body', count++);
return (
<div>{count}</div>
);
};
export default Body;
(Regardless of where Header change: true
appears)
Header change : false
api done
Body render 0
Header change : true
Header
is included)Header change : false
api done
Body render 0
Header change : true
Body render 1 // Unexpected extra render
It seems like Body
is rendering twice when Header
updates its state.
You can try this issue in the sandbox:
🔗 https://codesandbox.io/p/sandbox/react-query-suspense-twice-7t2rx7
Note:
Since the API call duration is not perfectly consistent, you may need to refresh the page a few times to reproduce the issue.
The Header
implementation in this demo differs from the sample code, but the issue and behavior remain the same.
Why does Body
re-render when Header
updates its state, even though they are sibling components and not directly connected?
Why does the timing of the API fetch and setTimeout
affect this behavior?
How can I prevent Body
from rendering twice while keeping Suspense
for useSuspenseQuery
?
Fascinating issue. It doesn’t appear when we downgrade to React18. I think it could be related to the pre-warming of suspense siblings in v19. But we’d have to ask the react team. Could also be a legit bug in react.