useBaseQuery fetches the old query key when an <Activity> tree is revealed
I maintainer di solito rispondono entro 1 giorno
@lazerg ci sta già lavorando.
Dal 21/10/2026.
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 35/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Ferma
- Stack tecnologico
- react, typescript
- Ambito
- frontend
Direzione di ricerca
Start in packages/react-query/src/useBaseQuery.ts and compare its subscription with the later observer.setOptions effect; inspect the corresponding useQueries flow and the referenced queryObserver.ts listener check. Reproduce with the Vitest and Testing Library example in the issue, then verify revealing the hidden tree fetches only the current key. An open linked pull request (#12230) is already addressing this issue.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Describe the bug
If a query's key changes while its component is inside <Activity mode="hidden">, revealing the tree fetches the old key once before it switches to the new one.
useBaseQuery hands the changed options to its observer in a passive effect that is declared after the store subscription (packages/react-query/src/useBaseQuery.ts, same on main):
React.useSyncExternalStore(
React.useCallback(
(onStoreChange) => {
const unsubscribe = shouldSubscribe
? observer.subscribe(notifyManager.batchCalls(onStoreChange))
: noop
observer.updateResult()
return unsubscribe
},
[observer, shouldSubscribe],
),
() => observer.getCurrentResult(),
() => observer.getCurrentResult(),
)
React.useEffect(() => {
observer.setOptions(defaultedOptions)
}, [defaultedOptions, observer])
While the tree is hidden, React re-renders it, so defaultedOptions already holds the new key, but it runs no effects. On reveal, effects mount in declaration order:
- The subscription runs first.
observer.subscribe→onSubscribe→shouldFetchOnMountreads the observer's previous query, which is stale by now, and fetches it. - Only then does
setOptionsmove the observer to the new key, which is fetched as well.
useQueries has the same order: observer.setQueries runs in a useEffect after the subscription.
This is the query side of #11171, where <Activity> re-subscription left a useMutation stuck. That one was fixed in MutationObserver.onSubscribe (#11172, #11217).
Your minimal, reproducible example
A Vitest + Testing Library case (jsdom), no external link:
// @vitest-environment jsdom
import { Activity, Suspense } from 'react'
import { QueryClient, QueryClientProvider, useSuspenseQuery } from '@tanstack/react-query'
import { act, render, screen } from '@testing-library/react'
import { expect, it } from 'vitest'
const fetched: string[] = []
function List({ filter }: { filter: string }) {
const { data } = useSuspenseQuery({
queryKey: ['list', filter],
queryFn: async () => {
fetched.push(filter)
return filter
},
})
return <p>{data}</p>
}
it('does not fetch the old key when an Activity is revealed', async () => {
const client = new QueryClient()
const view = (mode: 'visible' | 'hidden', filter: string) => (
<QueryClientProvider client={client}>
<Activity mode={mode}>
<Suspense fallback="loading">
<List filter={filter} />
</Suspense>
</Activity>
</QueryClientProvider>
)
const { rerender } = render(view('visible', 'a'))
await screen.findByText('a')
// Change the key while hidden: the hidden tree re-renders with 'b', but no effect runs.
rerender(view('hidden', 'b'))
// Let both queries go stale (suspense uses a 1s minimum staleTime).
await act(() => new Promise((resolve) => setTimeout(resolve, 1100)))
rerender(view('visible', 'b'))
await screen.findByText('b')
await act(() => new Promise((resolve) => setTimeout(resolve, 50)))
// 'a' was fetched once, on the first render. Revealing the tree should not fetch it again.
expect(fetched.filter((key) => key === 'a')).toEqual(['a'])
})
On 5.101.2 this fails with expected [ 'a', 'a' ] to deeply equal [ 'a' ].
Steps to reproduce
- Render a component that calls
useSuspenseQuery(oruseQuery) inside<Activity mode="visible">. - Switch the Activity to
hidden, and change the query key in the same update. - Wait past
staleTime(1s under suspense). - Switch the Activity back to
visible. - The
queryFnruns for the old key, then for the new key.
Expected behavior
Revealing the tree should fetch only the current key. The old key is no longer rendered, and no observer should start a fetch for it.
In our app, the old key named a group the user had just deleted on another tab. Every return to the tab sent requests for that group's records, and they came back 404.
How often does this bug happen?
Every time
Platform
- OS: macOS
- Browser: Chromium (also reproduced in jsdom, above)
Tanstack Query adapter
react-query
TanStack Query version
5.101.2 (the code is unchanged on main)
TypeScript version
5.9.3
Additional context
-
React / React DOM 19.2.3, Next.js 16.3.8 (App Router).
-
A fix that works for us: sync the options in a layout effect. Layout effects run before passive effects, including when an
<Activity>tree is revealed.setOptionsdoes not fetch while the observer has no listeners (queryObserver.ts,const mounted = this.hasListeners()). So the passive subscription then starts on the current query:React.useLayoutEffect(() => { observer.setOptions(defaultedOptions) }, [defaultedOptions, observer])We apply the same change to
observer.setQueriesinuseQueries. With that patch on 5.101.2, the case above passes, and so do the suites of our two React apps that use TanStack Query (about 6,000 tests). -
Another option would be to update the observer's query inside the subscribe callback, before
observer.subscribe, with the options from the latest render.
- Lingua principale
- TypeScript
- Stelle
- 50.4k
- Fork
- 4.3k
- Merge medio
- 9h 18m
- PR unite (30g)
- 599
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di TanStack/query
-
QueryClient.isFetching and useIsFetching accept a fetchStatus filter and ignore itForse già presa @Apu-Emdad l’ha presa 8 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
solid-query: STRICT_READ_UNTRACKED on Solid 2 — client()/options() read in component body (useMutation, useBaseQuery)Forse di nuovo libera @LeonxLJX l’ha presa 48 giorni fa e non c’è nessuna pull request aperta. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
TanStack/query#11358 · 2 commenti · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
-
[query-broadcast-client-experimental] broadcastQueryClient: an `added` event overwrites a receiving query that has no data (e.g. in error) with the sender's empty stateForse già presa @lazerg l’ha presa 2 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 25/100
I maintainer di solito rispondono entro 1 giorno
-
solid-query 6.0.0-rc.5: onCacheEvent throws NotReadyError during a Solid 2 (rc.14+) transition landingForse già presa @UzumakiODZ l’ha presa 7 giorni fa. Aperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 28/100
TanStack/query#12031 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Explicit gcTime schedules a GC timer during SSR, retaining the whole render in memoryForse già presa @AdzerKI l’ha presa 8 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 25/100
TanStack/query#12025 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di TanStack/query
Issue simili
-
数据界面的横向滚动条缺失Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
zilliztech/attu#1037 ·
-
clawsweeper:fix-shape-clear clawsweeper:queueable-fix clawsweeper:source-repro impact:ux-friction issue-rating: 🦞 diamond lobster no-stale P3
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
openclaw/openclaw#181212 · 1 commento · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
apify/apify-mcp-server#1684 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
Automattic/data-liberation-agent#953 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
siteboon/claudecodeui#1642 ·
I maintainer di solito rispondono entro 5 giorni