Query caches error result and never calls queryFn
Describe the bug
Hey,
So I found a very specific conditions, in which useQuery will cache an error thrown by queryFn, and then from there on out always return that error never calling queryFn again.
As you can see in the example from Stackblitz below, it only occurs when 2 things are set:
staleTime: Infinity
as well as this weird counter in the component itself, right after mounting.
useEffect(() => {
console.log('MyComponent MOUNT');
setCount(1);
return () => console.log('MyComponent UNMOUNT ***');
}, []);
This obviously is a simplified use-case meant to reproduce a bug, but one can imagine in a complex app there will be a hook that triggers component re-render on component mount.
Your minimal, reproducible example
https://stackblitz.com/edit/vitejs-vite-zhvjhgqf?file=src%2FApp.jsx
Steps to reproduce
- Go to Stackblitz
- Open browser DevTools and observe console output.
- Look for About to fetch! Count: N
- Click Manual Retry until the counter N reaches 3, at that point the queryFn will try to fetch from a non-existing endpoint resulting in an error thrown
- The ErrorBoundary catches the error and displays fallback component.
Expected behavior
When you hit "Try Again" on the screen the expected behavior is that the queryFn is invoked again and About to fetch! Count: 4 is printed in the console but that never happens.
From this point on, useQuery cannot escape the previously returned error and it'll forever be failing
How often does this bug happen?
Every time
Screenshots or Videos
No response
Platform
- macOS
- Chrome 140
- "@tanstack/react-query": "^5.90.2",
Tanstack Query adapter
react-query
TanStack Query version
v5.90.2
TypeScript version
NA
Additional context
No response
When the error boundary resets, it’s a signal to us that we can continue re-fetching. We have to temporarily stop this from happening because of how react re-renders the component again even though we throw an error. That’s the whole reason why we have a QueryErrorResetBoundary - I wish we wouldn’t need that at all.
We also have an effect that clears that re-set:
https://github.com/TanStack/query/blob/295a5d586f40176a7452c8e4b7ff8c5f39905caf/packages/react-query/src/errorBoundaryUtils.ts#L41-L47
This runs after the component has rendered once, where we expect it to have put the Query into a pending state already.
I don’t fully understand why your setState in an effect somehow seems to stop to put the query back to pending, but when the component re-renders because of the setCount, the reset boundary is already cleared so the error will just be thrown again.
It’s worth noticing that if you move the setState from the effect into render, which is the recommended approach (see: https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes), things seem to work fine:
https://stackblitz.com/edit/vitejs-vite-vswpetwv?file=src%2FApp.jsx
While I agree you can move setState call out of useEffect that wasn't really the point of this example. I imagine things start to work when you move setState into the render part of the component because of this part of the React documentation:
When you update a component during rendering, React throws away the returned JSX and immediately retries rendering.
In reality though, the bug still can occur in situations that are outside of our control -- where I can't move logic outside of useEffect.
Jotai is actually causing this behaviour to happen and is why I raised this bug. Simply subscribing to an atom makes the bug appear, I imagine because useAtom makes the component re-render the first time.
Placing this inside MyComponent will cause the bug to occur, even without using the atom value
const myAtomValue = useAtomValue(myAtom);
Here's the likely cause to illustrate and confirm my suspicion: https://github.com/pmndrs/jotai/blob/46610647b03bd3f66eff13dd556cbaaf63f2cbcb/src/react/useAtomValue.ts#L167
@TkDodo any further thoughts on above?
I don’t fully understand why your setState in an effect somehow seems to stop to put the query back to pending
Please feel free to investigate this, I don’t have the bandwidth atm.
Hi team 👋,
I'd like to work on this issue: Query caches error result and never calls queryFn (#9728).
Planned steps:
- Reproduce the issue locally with a minimal test case / reproduction.
- Add unit/integration tests that assert the cache behaves correctly after an error (i.e., queryFn is retried / called again according to retry settings).
- Trace the cache lifecycle to identify where an errored state becomes 'stuck' and implement a fix.
- Add regression tests and update/extend docs or comments if necessary.
Environment I'll use:
- Fork repo → create branch
fix/9728-retry-after-error - Run the repository's test suite and linters; follow
CONTRIBUTING.mdfor dev setup.
Before I open a PR I’ll post the minimal repro here for maintainers to confirm it matches expected behavior. Please assign this issue to me if that's OK, or let me know if someone else is already working on it.
Thanks — looking forward to contributing! 🙏