Skip to content

Pending manual refetch is cancelled on component unmount #9158

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
SaebAmini opened this issue May 17, 2025 · 3 comments
Closed

Pending manual refetch is cancelled on component unmount #9158

SaebAmini opened this issue May 17, 2025 · 3 comments

Comments

@SaebAmini
Copy link

SaebAmini commented May 17, 2025

Describe the bug

  • I have a query in component A with refetchOnMount: false for manual refetch control, as its data comes from an expensive fetch
  • In component B, the query used by component A is manually refetched under certain conditions (e.g. a mutation that we know will change component A's data)
  • If component A is mounted then unmounted while this refetch is in progress, the in-flight request is cancelled.

Your minimal, reproducible example

https://codesandbox.io/p/sandbox/weathered-hooks-qd3v7w

Steps to reproduce

  1. Navigate to Component A. The mock expensive call is made as expected and resolves after 3 seconds.
  2. Navigate to Component B and push the button "Refetch Query".
  3. Within the 3 seconds, navigate to Component A, then back to Component B.
  4. The pending manual refetch fired from Component B is cancelled.

Expected behavior

The manual refetch fired from Component B should not be cancelled or impacted by Component A's mount lifetime, however, it is cancelled when Component A is unmounted.

Why do I expect this behaviour:

  1. The next time Component A is mounted, it will have stale data.
  2. The query could also be used in other components. If Component A's unmounting cancels the explicit refetch fired elsewhere, that means other components will also keep having the stale data when they mount.
  3. It doesn't semantically make sense to me that Component A's lifetime should impact a query fired outside of it unless explicitly requested.

Simply not using an abort signal to work around this is not a suitable option, because there are still valid scenarios when the pending requests should be cancelled otherwise, e.g. in the case of duplicate in-flight requests.

Platform

  • OS: Windows
  • Browser: Edge
  • Version: 136.0.3240.64

Tanstack Query adapter

react-query

TanStack Query version

5.76.1

TypeScript version

4.4.4

@TkDodo
Copy link
Collaborator

TkDodo commented May 19, 2025

We don’t keep track of the reason that a queryFn was executed, in that sense, all fetches are equal, no matter if it happens because a component “mounted” or because you called refetch() imperatively.

The way query cancellation works is that is cancels a query as soon as the last observer unmounts, if you opt-in to it by using the AbortSignal. We only know that a query is “used” because it has observers (useQuery creates an observer).

In your example, the only observer is in Component A, so as soon as that one is unmounted, the Query is seen as unused or “inactive”. You should also see it show up as “inactive” in the query devtools. Being inactive has other implications, too, for example, inactive queries are eligible for garbage collection after gcTime has elapsed, so that data might be removed from the cache.

Not sure what your scenario is but the best solution here would be to also have Component B use the query with useQuery. After all, if Component B doesn’t use the Query, we have no reason to keep data around for it, and that includes triggering the AbortSignal.

@TkDodo TkDodo closed this as not planned Won't fix, can't repro, duplicate, stale May 19, 2025
@SaebAmini
Copy link
Author

Thanks for chiming in @TkDodo, the info on how things are designed and work gives helpful context.

We don’t keep track of the reason that a queryFn was executed, in that sense, all fetches are equal, no matter if it happens because a component “mounted” or because you called refetch() imperatively

Do you think this should be considered, though? Semantically, wouldn't it make sense for an imperative refetch to be treated differently and not be implicitly overruled by another component's lifecycle?

Not sure what your scenario is but the best solution here would be to also have Component B use the query with useQuery. After all, if Component B doesn’t use the Query, we have no reason to keep data around for it, and that includes triggering the AbortSignal.

My scenario is firing refetches from a "cache buster", outside of any component. It sets up refetches for expensive queries via QueryClient.setMutationDefaults for mutations that I know will invalidate the query cache. With the design as is, I will have to move this into a React component/context to play nice with the lifecycle observation.

@SaebAmini
Copy link
Author

@TkDodo Here's another interesting example where the behaviour might be questionable. I've now updated Component B to use the query with useQuery:

https://qd3v7w.csb.app/component-b

When you refetch the query in Component B, and then navigate to Component A, the pending imperative refetch is cancelled, and both Components A and B now remain with stale data, despite both components "using" the query.

So it seems the way around it is not only for Component B to use the query, but also to be a parent of Component A, because in between the time one component unmounts and the other one mounts and the query is considered "used", it's already cancelled.

IMHO, it's worth considering treating imperative refetches differently. I think it makes more sense that when something is imperatively done, that it should only be imperatively cancelled, or by exception. But I realise I'm not the design authority on this repo - just sharing my 2C :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants