diff --git a/docs/reference/QueryClient.md b/docs/reference/QueryClient.md index 13bde1ffad0..8ede22a6a33 100644 --- a/docs/reference/QueryClient.md +++ b/docs/reference/QueryClient.md @@ -64,6 +64,16 @@ Its available methods are: - Optional - Define defaults for all queries and mutations using this queryClient. - You can also define defaults to be used for [hydration](../framework/react/reference/hydration.md) +- `isServer?: boolean` + - Optional + - Overrides environment detection for this client. + - Defaults to the exported `isServer` value from `@tanstack/query-core`. + - Use this when your runtime does not match the default detection. + +**Properties** + +- `queryClient.isServer: boolean` + - The resolved server detection used by this `QueryClient` instance. ## `queryClient.fetchQuery` diff --git a/packages/preact-query/src/__tests__/ssr-hydration.test.tsx b/packages/preact-query/src/__tests__/ssr-hydration.test.tsx index 10154d01890..6e3bcd8cf8a 100644 --- a/packages/preact-query/src/__tests__/ssr-hydration.test.tsx +++ b/packages/preact-query/src/__tests__/ssr-hydration.test.tsx @@ -12,7 +12,6 @@ import { hydrate, useQuery, } from '..' -import { setIsServer } from './utils' const PreactHydrate = (element: VNode, container: Element) => { act(() => { @@ -62,11 +61,10 @@ describe('Server side rendering with de/rehydration', () => { } // -- Server part -- - setIsServer(true) - const prefetchCache = new QueryCache() const prefetchClient = new QueryClient({ queryCache: prefetchCache, + isServer: true, }) await prefetchClient.prefetchQuery({ queryKey: ['success'], @@ -76,6 +74,7 @@ describe('Server side rendering with de/rehydration', () => { const renderCache = new QueryCache() const renderClient = new QueryClient({ queryCache: renderCache, + isServer: true, }) hydrate(renderClient, dehydratedStateServer) const markup = renderToString( @@ -85,7 +84,6 @@ describe('Server side rendering with de/rehydration', () => { ) const stringifiedState = JSON.stringify(dehydratedStateServer) renderClient.clear() - setIsServer(false) const expectedMarkup = 'SuccessComponent - status:success fetching:true data:success' @@ -140,10 +138,10 @@ describe('Server side rendering with de/rehydration', () => { } // -- Server part -- - setIsServer(true) const prefetchCache = new QueryCache() const prefetchClient = new QueryClient({ queryCache: prefetchCache, + isServer: true, }) await prefetchClient.prefetchQuery({ queryKey: ['error'], @@ -153,6 +151,7 @@ describe('Server side rendering with de/rehydration', () => { const renderCache = new QueryCache() const renderClient = new QueryClient({ queryCache: renderCache, + isServer: true, }) hydrate(renderClient, dehydratedStateServer) const markup = renderToString( @@ -162,7 +161,6 @@ describe('Server side rendering with de/rehydration', () => { ) const stringifiedState = JSON.stringify(dehydratedStateServer) renderClient.clear() - setIsServer(false) const expectedMarkup = 'ErrorComponent - status:pending fetching:true data:undefined' @@ -216,11 +214,9 @@ describe('Server side rendering with de/rehydration', () => { } // -- Server part -- - setIsServer(true) - - const prefetchClient = new QueryClient() + const prefetchClient = new QueryClient({ isServer: true }) const dehydratedStateServer = dehydrate(prefetchClient) - const renderClient = new QueryClient() + const renderClient = new QueryClient({ isServer: true }) hydrate(renderClient, dehydratedStateServer) const markup = renderToString( @@ -229,7 +225,6 @@ describe('Server side rendering with de/rehydration', () => { ) const stringifiedState = JSON.stringify(dehydratedStateServer) renderClient.clear() - setIsServer(false) const expectedMarkup = 'SuccessComponent - status:pending fetching:true data:undefined' diff --git a/packages/preact-query/src/__tests__/ssr.test.tsx b/packages/preact-query/src/__tests__/ssr.test.tsx index d543a57501f..907b69328b7 100644 --- a/packages/preact-query/src/__tests__/ssr.test.tsx +++ b/packages/preact-query/src/__tests__/ssr.test.tsx @@ -11,18 +11,15 @@ import { useMutationState, useQuery, } from '..' -import { setIsServer } from './utils' describe('Server Side Rendering', () => { - setIsServer(true) - let queryCache: QueryCache let queryClient: QueryClient beforeEach(() => { vi.useFakeTimers() queryCache = new QueryCache() - queryClient = new QueryClient({ queryCache }) + queryClient = new QueryClient({ queryCache, isServer: true }) }) afterEach(() => { diff --git a/packages/preact-query/src/__tests__/utils.tsx b/packages/preact-query/src/__tests__/utils.tsx index d4f1dba05e9..a175de773a7 100644 --- a/packages/preact-query/src/__tests__/utils.tsx +++ b/packages/preact-query/src/__tests__/utils.tsx @@ -1,4 +1,3 @@ -import * as utils from '@tanstack/query-core' import { act, render } from '@testing-library/preact' import type { ComponentChildren, VNode } from 'preact' import { useEffect, useState } from 'preact/hooks' @@ -57,18 +56,3 @@ export function setActTimeout(fn: () => void, ms?: number) { }) }, ms) } - -// This monkey-patches the isServer-value from utils, -// so that we can pretend to be in a server environment -export function setIsServer(isServer: boolean) { - const original = utils.isServer - Object.defineProperty(utils, 'isServer', { - get: () => isServer, - }) - - return () => { - Object.defineProperty(utils, 'isServer', { - get: () => original, - }) - } -} diff --git a/packages/preact-query/src/useBaseQuery.ts b/packages/preact-query/src/useBaseQuery.ts index 84d75679162..5bc05069535 100644 --- a/packages/preact-query/src/useBaseQuery.ts +++ b/packages/preact-query/src/useBaseQuery.ts @@ -1,4 +1,4 @@ -import { isServer, noop, notifyManager } from '@tanstack/query-core' +import { noop, notifyManager } from '@tanstack/query-core' import type { QueryClient, QueryKey, @@ -147,7 +147,7 @@ export function useBaseQuery< if ( defaultedOptions.experimental_prefetchInRender && - !isServer && + !client.isServer && willFetch(result, isRestoring) ) { const promise = isNewCacheEntry diff --git a/packages/query-core/src/__tests__/focusManager.test.tsx b/packages/query-core/src/__tests__/focusManager.test.tsx index 3783361fb04..508e2b4c0fc 100644 --- a/packages/query-core/src/__tests__/focusManager.test.tsx +++ b/packages/query-core/src/__tests__/focusManager.test.tsx @@ -1,6 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, test, vi } from 'vitest' import { FocusManager } from '../focusManager' -import { setIsServer } from './utils' describe('focusManager', () => { let focusManager: FocusManager @@ -55,17 +54,22 @@ describe('focusManager', () => { }) test('cleanup (removeEventListener) should not be called if window is not defined', () => { - const restoreIsServer = setIsServer(true) - + const windowSpy = vi.spyOn(globalThis, 'window', 'get') + windowSpy.mockImplementation( + () => undefined as unknown as Window & typeof globalThis, + ) const removeEventListenerSpy = vi.spyOn(globalThis, 'removeEventListener') - const unsubscribe = focusManager.subscribe(() => undefined) + const subscribe = () => focusManager.subscribe(() => undefined) + + expect(subscribe).not.toThrow() + const unsubscribe = subscribe() unsubscribe() expect(removeEventListenerSpy).not.toHaveBeenCalled() - restoreIsServer() + windowSpy.mockRestore() }) test('cleanup (removeEventListener) should not be called if window.addEventListener is not defined', () => { diff --git a/packages/query-core/src/__tests__/onlineManager.test.tsx b/packages/query-core/src/__tests__/onlineManager.test.tsx index 20a9439de9f..1a5245bfc80 100644 --- a/packages/query-core/src/__tests__/onlineManager.test.tsx +++ b/packages/query-core/src/__tests__/onlineManager.test.tsx @@ -1,6 +1,5 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' import { OnlineManager } from '../onlineManager' -import { setIsServer } from './utils' describe('onlineManager', () => { let onlineManager: OnlineManager @@ -64,17 +63,22 @@ describe('onlineManager', () => { }) test('cleanup (removeEventListener) should not be called if window is not defined', () => { - const restoreIsServer = setIsServer(true) - + const windowSpy = vi.spyOn(globalThis, 'window', 'get') + windowSpy.mockImplementation( + () => undefined as unknown as Window & typeof globalThis, + ) const removeEventListenerSpy = vi.spyOn(globalThis, 'removeEventListener') - const unsubscribe = onlineManager.subscribe(() => undefined) + const subscribe = () => onlineManager.subscribe(() => undefined) + + expect(subscribe).not.toThrow() + const unsubscribe = subscribe() unsubscribe() expect(removeEventListenerSpy).not.toHaveBeenCalled() - restoreIsServer() + windowSpy.mockRestore() }) test('cleanup (removeEventListener) should not be called if window.addEventListener is not defined', () => { diff --git a/packages/query-core/src/__tests__/query.test.tsx b/packages/query-core/src/__tests__/query.test.tsx index 93e5ea515f8..72b60438a73 100644 --- a/packages/query-core/src/__tests__/query.test.tsx +++ b/packages/query-core/src/__tests__/query.test.tsx @@ -13,7 +13,7 @@ import { hydrate, } from '..' import { hashQueryKeyByOptions } from '../utils' -import { mockOnlineManagerIsOnline, setIsServer } from './utils' +import { mockOnlineManagerIsOnline } from './utils' import type { QueryCache, QueryFunctionContext, @@ -867,12 +867,12 @@ describe('query', () => { }) it('should not retry on the server', async () => { - const resetIsServer = setIsServer(true) + const serverClient = new QueryClient({ isServer: true }) const key = queryKey() let count = 0 - const observer = new QueryObserver(queryClient, { + const observer = new QueryObserver(serverClient, { queryKey: key, queryFn: () => { count++ @@ -883,8 +883,6 @@ describe('query', () => { await observer.refetch() expect(count).toBe(1) - - resetIsServer() }) test('constructor should call initialDataUpdatedAt if defined as a function', async () => { diff --git a/packages/query-core/src/__tests__/queryClient.test.tsx b/packages/query-core/src/__tests__/queryClient.test.tsx index 2676717fb62..1902a5a86bc 100644 --- a/packages/query-core/src/__tests__/queryClient.test.tsx +++ b/packages/query-core/src/__tests__/queryClient.test.tsx @@ -58,6 +58,19 @@ describe('queryClient', () => { expect(newQuery?.options.gcTime).toBe(Infinity) }) + test('should allow overriding server detection for gcTime defaults', async () => { + const key = queryKey() + const testClient = new QueryClient({ isServer: true }) + + await testClient.prefetchQuery({ + queryKey: key, + queryFn: () => Promise.resolve('data'), + }) + + const newQuery = testClient.getQueryCache().find({ queryKey: key }) + expect(newQuery?.gcTime).toBe(Infinity) + }) + test('should get defaultOptions', () => { const queryFn = () => 'data' const defaultOptions = { queries: { queryFn } } diff --git a/packages/query-core/src/__tests__/utils.ts b/packages/query-core/src/__tests__/utils.ts index f9ef89d7650..07ff00e652f 100644 --- a/packages/query-core/src/__tests__/utils.ts +++ b/packages/query-core/src/__tests__/utils.ts @@ -1,6 +1,5 @@ import { vi } from 'vitest' import { onlineManager } from '..' -import * as utils from '../utils' import type { MockInstance } from 'vitest' import type { MutationOptions, QueryClient } from '..' @@ -20,18 +19,3 @@ export function executeMutation( .build(queryClient, options) .execute(variables) } - -// This monkey-patches the isServer-value from utils, -// so that we can pretend to be in a server environment -export function setIsServer(isServer: boolean) { - const original = utils.isServer - Object.defineProperty(utils, 'isServer', { - get: () => isServer, - }) - - return () => { - Object.defineProperty(utils, 'isServer', { - get: () => original, - }) - } -} diff --git a/packages/query-core/src/focusManager.ts b/packages/query-core/src/focusManager.ts index cb0d8598713..21be2612d3a 100644 --- a/packages/query-core/src/focusManager.ts +++ b/packages/query-core/src/focusManager.ts @@ -1,5 +1,4 @@ import { Subscribable } from './subscribable' -import { isServer } from './utils' type Listener = (focused: boolean) => void @@ -18,7 +17,7 @@ export class FocusManager extends Subscribable { this.#setup = (onFocus) => { // addEventListener does not exist in React Native, but window does // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (!isServer && window.addEventListener) { + if (typeof window !== 'undefined' && window.addEventListener) { const listener = () => onFocus() // Listen to visibilitychange window.addEventListener('visibilitychange', listener, false) diff --git a/packages/query-core/src/index.ts b/packages/query-core/src/index.ts index a7763cf6483..dfc6a8440cd 100644 --- a/packages/query-core/src/index.ts +++ b/packages/query-core/src/index.ts @@ -27,7 +27,6 @@ export { } from './timeoutManager' export { hashKey, - isServer, keepPreviousData, matchMutation, matchQuery, @@ -39,6 +38,12 @@ export { } from './utils' export type { MutationFilters, QueryFilters, SkipToken, Updater } from './utils' +/** @deprecated + * use `queryClient.isServer` instead. + * Override per client with `new QueryClient({ isServer })`. + */ +export const isServer = typeof window === 'undefined' || 'Deno' in globalThis + export { streamedQuery as experimental_streamedQuery } from './streamedQuery' // Types diff --git a/packages/query-core/src/mutation.ts b/packages/query-core/src/mutation.ts index 2483b563366..1e3ecde5c45 100644 --- a/packages/query-core/src/mutation.ts +++ b/packages/query-core/src/mutation.ts @@ -118,7 +118,7 @@ export class Mutation< ): void { this.options = options - this.updateGcTime(this.options.gcTime) + this.updateGcTime(this.options.gcTime, this.#client.isServer) } get meta(): MutationMeta | undefined { @@ -199,6 +199,7 @@ export class Mutation< retry: this.options.retry ?? 0, retryDelay: this.options.retryDelay, networkMode: this.options.networkMode, + isServer: this.#client.isServer, canRun: () => this.#mutationCache.canRun(this), }) diff --git a/packages/query-core/src/onlineManager.ts b/packages/query-core/src/onlineManager.ts index daf77d5a4c2..6c59a14b908 100644 --- a/packages/query-core/src/onlineManager.ts +++ b/packages/query-core/src/onlineManager.ts @@ -1,5 +1,4 @@ import { Subscribable } from './subscribable' -import { isServer } from './utils' type Listener = (online: boolean) => void type SetupFn = (setOnline: Listener) => (() => void) | undefined @@ -15,7 +14,7 @@ export class OnlineManager extends Subscribable { this.#setup = (onOnline) => { // addEventListener does not exist in React Native, but window does // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (!isServer && window.addEventListener) { + if (typeof window !== 'undefined' && window.addEventListener) { const onlineListener = () => onOnline(true) const offlineListener = () => onOnline(false) // Listen to online diff --git a/packages/query-core/src/query.ts b/packages/query-core/src/query.ts index b29b64c0e3e..c593ba152d0 100644 --- a/packages/query-core/src/query.ts +++ b/packages/query-core/src/query.ts @@ -180,11 +180,11 @@ export class Query< super() this.#abortSignalConsumed = false + this.#client = config.client + this.#cache = this.#client.getQueryCache() this.#defaultOptions = config.defaultOptions this.setOptions(config.options) this.observers = [] - this.#client = config.client - this.#cache = this.#client.getQueryCache() this.queryKey = config.queryKey this.queryHash = config.queryHash this.#initialState = getDefaultState(this.options) @@ -204,7 +204,7 @@ export class Query< ): void { this.options = { ...this.#defaultOptions, ...options } - this.updateGcTime(this.options.gcTime) + this.updateGcTime(this.options.gcTime, this.#client.isServer) // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (this.state && this.state.data === undefined) { @@ -539,6 +539,7 @@ export class Query< retry: context.options.retry, retryDelay: context.options.retryDelay, networkMode: context.options.networkMode, + isServer: this.#client.isServer, canRun: () => true, }) diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index 80cc36668aa..47d6063e36a 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -67,6 +67,11 @@ export class QueryClient { #mountCount: number #unsubscribeFocus?: () => void #unsubscribeOnline?: () => void + /** + * Resolved server detection for this client instance. + * Derived from `QueryClientConfig.isServer` or the default `isServer` export. + */ + readonly isServer: boolean constructor(config: QueryClientConfig = {}) { this.#queryCache = config.queryCache || new QueryCache() @@ -75,6 +80,8 @@ export class QueryClient { this.#queryDefaults = new Map() this.#mutationDefaults = new Map() this.#mountCount = 0 + this.isServer = + config.isServer ?? (typeof window === 'undefined' || 'Deno' in globalThis) } mount(): void { diff --git a/packages/query-core/src/queryObserver.ts b/packages/query-core/src/queryObserver.ts index 463407a0737..f77970f730d 100644 --- a/packages/query-core/src/queryObserver.ts +++ b/packages/query-core/src/queryObserver.ts @@ -4,7 +4,6 @@ import { fetchState } from './query' import { Subscribable } from './subscribable' import { pendingThenable } from './thenable' import { - isServer, isValidTimeout, noop, replaceData, @@ -358,7 +357,11 @@ export class QueryObserver< this.#currentQuery, ) - if (isServer || this.#currentResult.isStale || !isValidTimeout(staleTime)) { + if ( + this.#client.isServer || + this.#currentResult.isStale || + !isValidTimeout(staleTime) + ) { return } @@ -389,7 +392,7 @@ export class QueryObserver< this.#currentRefetchInterval = nextInterval if ( - isServer || + this.#client.isServer || resolveEnabled(this.options.enabled, this.#currentQuery) === false || !isValidTimeout(this.#currentRefetchInterval) || this.#currentRefetchInterval === 0 diff --git a/packages/query-core/src/removable.ts b/packages/query-core/src/removable.ts index 8642ab36ec2..d6a7761d7e7 100644 --- a/packages/query-core/src/removable.ts +++ b/packages/query-core/src/removable.ts @@ -1,7 +1,9 @@ import { timeoutManager } from './timeoutManager' -import { isServer, isValidTimeout } from './utils' +import { isValidTimeout } from './utils' import type { ManagedTimerId } from './timeoutManager' +const DEFAULT_GC_TIME = 5 * 60 * 1000 + export abstract class Removable { gcTime!: number #gcTimeout?: ManagedTimerId @@ -20,11 +22,14 @@ export abstract class Removable { } } - protected updateGcTime(newGcTime: number | undefined): void { + protected updateGcTime( + newGcTime: number | undefined, + isServer: boolean, + ): void { // Default to 5 minutes (Infinity for server-side) if no gcTime is set this.gcTime = Math.max( this.gcTime || 0, - newGcTime ?? (isServer ? Infinity : 5 * 60 * 1000), + newGcTime ?? (isServer ? Infinity : DEFAULT_GC_TIME), ) } diff --git a/packages/query-core/src/retryer.ts b/packages/query-core/src/retryer.ts index f4ada851c9f..b954f2c2e43 100644 --- a/packages/query-core/src/retryer.ts +++ b/packages/query-core/src/retryer.ts @@ -1,7 +1,7 @@ import { focusManager } from './focusManager' import { onlineManager } from './onlineManager' import { pendingThenable } from './thenable' -import { isServer, sleep } from './utils' +import { sleep } from './utils' import type { Thenable } from './thenable' import type { CancelOptions, DefaultError, NetworkMode } from './types' @@ -17,6 +17,7 @@ interface RetryerConfig { retry?: RetryValue retryDelay?: RetryDelayValue networkMode: NetworkMode | undefined + isServer: boolean canRun: () => boolean } @@ -166,7 +167,7 @@ export function createRetryer( } // Do we need to retry the request? - const retry = config.retry ?? (isServer ? 0 : 3) + const retry = config.retry ?? (config.isServer ? 0 : 3) const retryDelay = config.retryDelay ?? defaultRetryDelay const delay = typeof retryDelay === 'function' diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index 4f3f4caed20..fa8ba51a070 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -1356,6 +1356,12 @@ export interface QueryClientConfig { queryCache?: QueryCache mutationCache?: MutationCache defaultOptions?: DefaultOptions + /** + * Overrides environment detection for this client. + * When `true`, defaults that depend on server detection behave as server-side. + * Defaults to the exported `isServer` value. + */ + isServer?: boolean } export interface DefaultOptions { diff --git a/packages/query-core/src/utils.ts b/packages/query-core/src/utils.ts index 27d44bc98b3..0e740b60fe2 100644 --- a/packages/query-core/src/utils.ts +++ b/packages/query-core/src/utils.ts @@ -86,8 +86,6 @@ export type QueryTypeFilter = 'all' | 'active' | 'inactive' // UTILS -export const isServer = typeof window === 'undefined' || 'Deno' in globalThis - export function noop(): void export function noop(): undefined export function noop() {} diff --git a/packages/react-query/src/__tests__/ssr-hydration.test.tsx b/packages/react-query/src/__tests__/ssr-hydration.test.tsx index 07f469b5684..3f65d89a955 100644 --- a/packages/react-query/src/__tests__/ssr-hydration.test.tsx +++ b/packages/react-query/src/__tests__/ssr-hydration.test.tsx @@ -10,7 +10,6 @@ import { hydrate, useQuery, } from '..' -import { setIsServer } from './utils' const ReactHydrate = (element: React.ReactElement, container: Element) => { let root: any @@ -63,11 +62,10 @@ describe('Server side rendering with de/rehydration', () => { } // -- Server part -- - setIsServer(true) - const prefetchCache = new QueryCache() const prefetchClient = new QueryClient({ queryCache: prefetchCache, + isServer: true, }) await prefetchClient.prefetchQuery({ queryKey: ['success'], @@ -77,6 +75,7 @@ describe('Server side rendering with de/rehydration', () => { const renderCache = new QueryCache() const renderClient = new QueryClient({ queryCache: renderCache, + isServer: true, }) hydrate(renderClient, dehydratedStateServer) const markup = ReactDOMServer.renderToString( @@ -86,7 +85,6 @@ describe('Server side rendering with de/rehydration', () => { ) const stringifiedState = JSON.stringify(dehydratedStateServer) renderClient.clear() - setIsServer(false) const expectedMarkup = 'SuccessComponent - status:success fetching:true data:success' @@ -141,10 +139,10 @@ describe('Server side rendering with de/rehydration', () => { } // -- Server part -- - setIsServer(true) const prefetchCache = new QueryCache() const prefetchClient = new QueryClient({ queryCache: prefetchCache, + isServer: true, }) await prefetchClient.prefetchQuery({ queryKey: ['error'], @@ -154,6 +152,7 @@ describe('Server side rendering with de/rehydration', () => { const renderCache = new QueryCache() const renderClient = new QueryClient({ queryCache: renderCache, + isServer: true, }) hydrate(renderClient, dehydratedStateServer) const markup = ReactDOMServer.renderToString( @@ -163,7 +162,6 @@ describe('Server side rendering with de/rehydration', () => { ) const stringifiedState = JSON.stringify(dehydratedStateServer) renderClient.clear() - setIsServer(false) const expectedMarkup = 'ErrorComponent - status:pending fetching:true data:undefined' @@ -217,11 +215,9 @@ describe('Server side rendering with de/rehydration', () => { } // -- Server part -- - setIsServer(true) - - const prefetchClient = new QueryClient() + const prefetchClient = new QueryClient({ isServer: true }) const dehydratedStateServer = dehydrate(prefetchClient) - const renderClient = new QueryClient() + const renderClient = new QueryClient({ isServer: true }) hydrate(renderClient, dehydratedStateServer) const markup = ReactDOMServer.renderToString( @@ -230,7 +226,6 @@ describe('Server side rendering with de/rehydration', () => { ) const stringifiedState = JSON.stringify(dehydratedStateServer) renderClient.clear() - setIsServer(false) const expectedMarkup = 'SuccessComponent - status:pending fetching:true data:undefined' diff --git a/packages/react-query/src/__tests__/ssr.test.tsx b/packages/react-query/src/__tests__/ssr.test.tsx index 8ca076c5478..9155c3a620d 100644 --- a/packages/react-query/src/__tests__/ssr.test.tsx +++ b/packages/react-query/src/__tests__/ssr.test.tsx @@ -13,18 +13,15 @@ import { useQueries, useQuery, } from '..' -import { setIsServer } from './utils' describe('Server Side Rendering', () => { - setIsServer(true) - let queryCache: QueryCache let queryClient: QueryClient beforeEach(() => { vi.useFakeTimers() queryCache = new QueryCache() - queryClient = new QueryClient({ queryCache }) + queryClient = new QueryClient({ queryCache, isServer: true }) }) afterEach(() => { diff --git a/packages/react-query/src/__tests__/utils.tsx b/packages/react-query/src/__tests__/utils.tsx index 3734c1caf67..b8fcbf7adf5 100644 --- a/packages/react-query/src/__tests__/utils.tsx +++ b/packages/react-query/src/__tests__/utils.tsx @@ -1,7 +1,6 @@ import { vi } from 'vitest' import * as React from 'react' import { act, render } from '@testing-library/react' -import * as utils from '@tanstack/query-core' import { QueryClientProvider, onlineManager } from '..' import type { QueryClient } from '..' import type { MockInstance } from 'vitest' @@ -55,18 +54,3 @@ export function setActTimeout(fn: () => void, ms?: number) { }) }, ms) } - -// This monkey-patches the isServer-value from utils, -// so that we can pretend to be in a server environment -export function setIsServer(isServer: boolean) { - const original = utils.isServer - Object.defineProperty(utils, 'isServer', { - get: () => isServer, - }) - - return () => { - Object.defineProperty(utils, 'isServer', { - get: () => original, - }) - } -} diff --git a/packages/react-query/src/useBaseQuery.ts b/packages/react-query/src/useBaseQuery.ts index 2a151fe1137..bed1ee7892e 100644 --- a/packages/react-query/src/useBaseQuery.ts +++ b/packages/react-query/src/useBaseQuery.ts @@ -1,7 +1,7 @@ 'use client' import * as React from 'react' -import { isServer, noop, notifyManager } from '@tanstack/query-core' +import { noop, notifyManager } from '@tanstack/query-core' import { useQueryClient } from './QueryClientProvider' import { useQueryErrorResetBoundary } from './QueryErrorResetBoundary' import { @@ -148,7 +148,7 @@ export function useBaseQuery< if ( defaultedOptions.experimental_prefetchInRender && - !isServer && + !client.isServer && willFetch(result, isRestoring) ) { const promise = isNewCacheEntry diff --git a/packages/vue-query/src/__tests__/queryClient.test.ts b/packages/vue-query/src/__tests__/queryClient.test.ts index c97be2db192..395e27bc599 100644 --- a/packages/vue-query/src/__tests__/queryClient.test.ts +++ b/packages/vue-query/src/__tests__/queryClient.test.ts @@ -4,6 +4,14 @@ import { QueryClient as QueryClientOrigin } from '@tanstack/query-core' import { QueryClient } from '../queryClient' import { infiniteQueryOptions } from '../infiniteQueryOptions' +type QueryClientPrototypeMethod = { + [TKey in keyof typeof QueryClientOrigin.prototype]: (typeof QueryClientOrigin.prototype)[TKey] extends ( + ...args: Array + ) => any + ? TKey + : never +}[keyof typeof QueryClientOrigin.prototype] + vi.mock('@tanstack/query-core', async () => { const actual = await vi.importActual<{ QueryClient: typeof QueryClientOrigin @@ -12,7 +20,7 @@ vi.mock('@tanstack/query-core', async () => { // Get the prototype methods dynamically const prototypeMethods = Object.getOwnPropertyNames( actual.QueryClient.prototype, - ).filter((prop): prop is keyof typeof actual.QueryClient.prototype => { + ).filter((prop): prop is QueryClientPrototypeMethod => { const descriptor = Object.getOwnPropertyDescriptor( actual.QueryClient.prototype, prop, diff --git a/packages/vue-query/src/queryClient.ts b/packages/vue-query/src/queryClient.ts index 7f9a0894bfe..8ff9f4433e7 100644 --- a/packages/vue-query/src/queryClient.ts +++ b/packages/vue-query/src/queryClient.ts @@ -35,13 +35,18 @@ import type { } from '@tanstack/query-core' export class QueryClient extends QC { + readonly isServer: boolean + constructor(config: QueryClientConfig = {}) { const vueQueryConfig = { defaultOptions: config.defaultOptions, queryCache: config.queryCache || new QueryCache(), mutationCache: config.mutationCache || new MutationCache(), + isServer: config.isServer, } super(vueQueryConfig) + this.isServer = + config.isServer ?? (typeof window === 'undefined' || 'Deno' in globalThis) } isRestoring?: Ref = ref(false) diff --git a/packages/vue-query/src/types.ts b/packages/vue-query/src/types.ts index eb793c811eb..fd3dc193868 100644 --- a/packages/vue-query/src/types.ts +++ b/packages/vue-query/src/types.ts @@ -75,4 +75,5 @@ export interface QueryClientConfig { queryCache?: QueryCache mutationCache?: MutationCache defaultOptions?: DefaultOptions + isServer?: boolean } diff --git a/packages/vue-query/src/vueQueryPlugin.ts b/packages/vue-query/src/vueQueryPlugin.ts index 590d994d304..e077af293e9 100644 --- a/packages/vue-query/src/vueQueryPlugin.ts +++ b/packages/vue-query/src/vueQueryPlugin.ts @@ -1,5 +1,4 @@ import { isVue2 } from 'vue-demi' -import { isServer } from '@tanstack/query-core' import { QueryClient } from './queryClient' import { getClientKey } from './utils' @@ -38,7 +37,7 @@ export const VueQueryPlugin = { client = new QueryClient(clientConfig) } - if (!isServer) { + if (!client.isServer) { client.mount() }