T
TanStack16mo ago
foreign-sapphire

Bug when switching tabs / windows when mutation is retrying

Steps to reproduce: - create new mutation with retry of 3 (or higher number), have the mutation perform fetch request and throw an error on status 500, and intentionally return status 500 from your BE - run the mutation on button click - go to the browser click the button to run the mutation and immediately switch tabs or switch windows and wait for 10-20 seconds Expected behavior: - the mutation would fail because of the error thrown due to the status 500 and it would retry for a few times and then change the isPending value to false and return something in the error value of the mutation Actual behavior: - the isPending value is stuck on true infinitely - the mutation is never retried (sometimes it retries only once) - the error variable has no value Additional notes: - when performing the same steps but not switching tabs/windows the retries are working and the isPending value is changed back to false and there's a value in the error variable, for some reason it's a bug that occurs only when switching tabs/windows... - Browser: Brave (latest) - OS: windows 10
7 Replies
quickest-silver
quickest-silver16mo ago
it's a hardcoded condition that queries and mutations pause when the window is not focussed.
foreign-sapphire
foreign-sapphireOP16mo ago
Paused or stopped? Even after refocusing on the tab the mutation isn't retrying, isn't failing and isn't resuming its execution, it's just stuck on pending and depending on the UI (in my case it's in a modal) the user could be stuck on the loading state and would be forced to refresh to make the app usable again.. It makes sense to prevent retries when the tab isn't in focus but I think it'd be great to have the last error returned in the error variable or at least perform reset on the mutation..
quickest-silver
quickest-silver16mo ago
It should resume again onFocus. If that isn't the case, please show a minimal reproduction with stackblitz or codesandbox
foreign-sapphire
foreign-sapphireOP16mo ago
It seems like it's stuck on the paused state (isPaused is true even after refocusing on the tab) I've been trying to replicate this issue in CodeSandBox and I can't find the root of the issue and replicate it, do you know what might cause the mutation to be stuck on paused? My code:
const mutation = useMutation({
mutationFn: async () => {
console.log("mutationFn");
throw new Error("test");
},
retry: (failureCount) => {
console.log("retry", failureCount);
return failureCount < 3;
},
});

return <div>
<Button
onClick={() => mutation.mutateAsync()}
disabled={mutation.isPending}
>
Run Mutation
</Button>
<div>isPaused: {String(mutation.isPaused)}</div>
<div>isPending: {String(mutation.isPending)}</div>
</div>
const mutation = useMutation({
mutationFn: async () => {
console.log("mutationFn");
throw new Error("test");
},
retry: (failureCount) => {
console.log("retry", failureCount);
return failureCount < 3;
},
});

return <div>
<Button
onClick={() => mutation.mutateAsync()}
disabled={mutation.isPending}
>
Run Mutation
</Button>
<div>isPaused: {String(mutation.isPaused)}</div>
<div>isPending: {String(mutation.isPending)}</div>
</div>
And I have this default settings on the query client:
const queryClient = new QueryClient({ defaultOptions: { mutations: { retry: 2 } } });
const queryClient = new QueryClient({ defaultOptions: { mutations: { retry: 2 } } });
quickest-silver
quickest-silver16mo ago
paused mutations should be automatically resumed once we gain the focus event: https://github.com/TanStack/query/blob/de2f862ba5c67ce80bda77fc3f1f874dbb325c01/packages/query-core/src/queryClient.ts#L80-L81 are you sure they aren't paused because you are offline ?
GitHub
query/packages/query-core/src/queryClient.ts at de2f862ba5c67ce80bd...
🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query. - TanStack/query
quickest-silver
quickest-silver16mo ago
this is how we determine if you are focused, but you can provide your own listener as well: https://github.com/TanStack/query/blob/2819dcda57b7f24366f9238d009ab3cb28639ac8/packages/query-core/src/focusManager.ts#L82
GitHub
query/packages/query-core/src/focusManager.ts at 2819dcda57b7f24366...
🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query. - TanStack/query
foreign-sapphire
foreign-sapphireOP16mo ago
EDIT: I removed the .next folder and restarted the dev server and it seems to be solved, IDK what that weird bug was/is, I hope it won't come back, if it will I'll try again to reproduce and come back here with more context.. Original comment: It seems like isFocused is true while isPaused is also true (both when I set my own listener with setEventListener and when I don't) I added the following code to keep track of the current isFocused state and print it to the UI:
const [isFocused, setIsFocused] = useState(false);

useEffect(() => {
const interval = setInterval(() => {
console.log("isFocused", focusManager.isFocused());
setIsFocused(focusManager.isFocused());
}, 1000);

return () => {
clearInterval(interval);
};
});
const [isFocused, setIsFocused] = useState(false);

useEffect(() => {
const interval = setInterval(() => {
console.log("isFocused", focusManager.isFocused());
setIsFocused(focusManager.isFocused());
}, 1000);

return () => {
clearInterval(interval);
};
});

Did you find this page helpful?