Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quiet-timers-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/query-core': patch
---

Do not schedule garbage collection on the server. A timer scheduled during server rendering captures the async context it was created in and keeps that whole render alive until it fires, while the client it would clean up is dropped with the response. Only queries and mutations with an explicit finite `gcTime` were affected, since the server default is already `Infinity`.
20 changes: 20 additions & 0 deletions packages/query-core/src/__tests__/query.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
QueryObserver,
dehydrate,
hydrate,
timeoutManager,
} from '..'
import { hashQueryKeyByOptions } from '../utils'
import { mockOnlineManagerIsOnline, setIsServer } from './utils'
Expand Down Expand Up @@ -983,6 +984,25 @@ describe('query', () => {
}
})

it('should not schedule garbage collection on the server, even with an explicit gcTime', () => {
const resetIsServer = setIsServer(true)
const scheduled = vi.spyOn(timeoutManager, 'setTimeout')

try {
const query = queryCache.build(queryClient, {
queryKey: queryKey(),
queryFn: () => 'data',
gcTime: 1000,
})

expect(query.gcTime).toBe(1000)
expect(scheduled).not.toHaveBeenCalled()
} finally {
scheduled.mockRestore()
resetIsServer()
}
})

it('constructor should call initialDataUpdatedAt if defined as a function', async () => {
const key = queryKey()

Expand Down
4 changes: 4 additions & 0 deletions packages/query-core/src/removable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export abstract class Removable {
protected scheduleGc(): void {
this.clearGcTimeout()

if (isServerEnvironment()) {
return
}

if (isValidTimeout(this.gcTime)) {
this.#gcTimeout = timeoutManager.setTimeout(() => {
this.optionalRemove()
Expand Down