Skip to content
Closed

isServer #10198

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
10 changes: 10 additions & 0 deletions docs/reference/QueryClient.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down
17 changes: 6 additions & 11 deletions packages/preact-query/src/__tests__/ssr-hydration.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
hydrate,
useQuery,
} from '..'
import { setIsServer } from './utils'

const PreactHydrate = (element: VNode, container: Element) => {
act(() => {
Expand Down Expand Up @@ -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'],
Expand All @@ -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(
Expand All @@ -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'
Expand Down Expand Up @@ -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'],
Expand All @@ -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(
Expand All @@ -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'
Expand Down Expand Up @@ -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(
<QueryClientProvider client={renderClient}>
Expand All @@ -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'
Expand Down
5 changes: 1 addition & 4 deletions packages/preact-query/src/__tests__/ssr.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
16 changes: 0 additions & 16 deletions packages/preact-query/src/__tests__/utils.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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,
})
}
}
4 changes: 2 additions & 2 deletions packages/preact-query/src/useBaseQuery.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isServer, noop, notifyManager } from '@tanstack/query-core'
import { noop, notifyManager } from '@tanstack/query-core'
import type {
QueryClient,
QueryKey,
Expand Down Expand Up @@ -147,7 +147,7 @@ export function useBaseQuery<

if (
defaultedOptions.experimental_prefetchInRender &&
!isServer &&
!client.isServer &&
willFetch(result, isRestoring)
) {
const promise = isNewCacheEntry
Expand Down
14 changes: 9 additions & 5 deletions packages/query-core/src/__tests__/focusManager.test.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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', () => {
Expand Down
14 changes: 9 additions & 5 deletions packages/query-core/src/__tests__/onlineManager.test.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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', () => {
Expand Down
8 changes: 3 additions & 5 deletions packages/query-core/src/__tests__/query.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
hydrate,
} from '..'
import { hashQueryKeyByOptions } from '../utils'
import { mockOnlineManagerIsOnline, setIsServer } from './utils'
import { mockOnlineManagerIsOnline } from './utils'
import type {
QueryCache,
QueryFunctionContext,
Expand Down Expand Up @@ -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++
Expand All @@ -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 () => {
Expand Down
13 changes: 13 additions & 0 deletions packages/query-core/src/__tests__/queryClient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 } }
Expand Down
16 changes: 0 additions & 16 deletions packages/query-core/src/__tests__/utils.ts
Original file line number Diff line number Diff line change
@@ -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 '..'

Expand All @@ -20,18 +19,3 @@ export function executeMutation<TVariables>(
.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,
})
}
}
3 changes: 1 addition & 2 deletions packages/query-core/src/focusManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Subscribable } from './subscribable'
import { isServer } from './utils'

type Listener = (focused: boolean) => void

Expand All @@ -18,7 +17,7 @@ export class FocusManager extends Subscribable<Listener> {
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)
Expand Down
7 changes: 6 additions & 1 deletion packages/query-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export {
} from './timeoutManager'
export {
hashKey,
isServer,
keepPreviousData,
matchMutation,
matchQuery,
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion packages/query-core/src/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
})

Expand Down
3 changes: 1 addition & 2 deletions packages/query-core/src/onlineManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Subscribable } from './subscribable'
import { isServer } from './utils'

type Listener = (online: boolean) => void
type SetupFn = (setOnline: Listener) => (() => void) | undefined
Expand All @@ -15,7 +14,7 @@ export class OnlineManager extends Subscribable<Listener> {
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
Expand Down
Loading
Loading