Facing an infinite render when a query fails using react-query. useQuery
should only re-fetch if queryKey
changes, or on request failure according to retry
parameter.
Why is queryFn
being re-executed?
Reproducible example: https://stackblitz.com/edit/ilias-react-query-loop?file=src%2Fmain.tsx
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
const useFailingQuery = () =>
useQuery({
queryKey: ['static'],
queryFn() {
console.log('Fetching...');
throw new Error('Fail');
return {};
},
});
const Component = () => {
useFailingQuery();
return <h1>Component</h1>;
};
const App = () => {
const { isLoading } = useFailingQuery();
// This causes infinite query retry
if (isLoading) {
return <span>Loading...</span>;
}
return <Component />;
};
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>
);
In the end, I need to render <Component />
only when useFailingQuery()
is finished loading, and render a page-wide skeleton while it is loading.
There is a setting called retryOnMount
(https://tanstack.com/query/v5/docs/framework/react/reference/useQuery#:~:text=retryOnMount%3A%20boolean) that is true by default. This is what is causing your loop. When the <Component />
mounts it triggers a refetch, the refetch makes isLoading
true for a render, which unmounts the component, next render causes isLoading
to be false, which mounts the component again and changes the isLoading
to true.
The solution to your problem is setting retryOnMount
to false in either the global config or the config for the particular query.
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false, retryOnMount: false } },
});