query icon indicating copy to clipboard operation
query copied to clipboard

Query caches error result and never calls queryFn

Open travikk opened this issue 4 months ago • 5 comments

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

  1. Go to Stackblitz
  2. Open browser DevTools and observe console output.
  3. Look for About to fetch! Count: N
  4. 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
  5. 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

travikk avatar Oct 04 '25 02:10 travikk

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

TkDodo avatar Oct 14 '25 07:10 TkDodo

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

travikk avatar Oct 14 '25 07:10 travikk

@TkDodo any further thoughts on above?

travikk avatar Nov 01 '25 03:11 travikk

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.

TkDodo avatar Nov 01 '25 14:11 TkDodo

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.md for 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! 🙏

xiangnuans avatar Nov 27 '25 14:11 xiangnuans