Skip to content
Merged
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
79 changes: 77 additions & 2 deletions src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
QueryKey,
QueryKeySerializerFunction,
ReactQueryConfig,
QueryConfig,
MutationConfig,
} from './types'

// TYPES
Expand All @@ -29,6 +31,22 @@ export const defaultQueryKeySerializerFn: QueryKeySerializerFunction = (
}
}

/**
* Config merging strategy
*
* When using hooks the config will be merged in the following order:
*
* 1. These defaults.
* 2. Defaults from the hook query cache.
* 3. Combined defaults from any config providers in the tree.
* 4. Query/mutation config provided to the hook.
*
* When using a query cache directly the config will be merged in the following order:
*
* 1. These defaults.
* 2. Defaults from the query cache.
* 3. Query/mutation config provided to the query cache method.
*/
export const DEFAULT_CONFIG: ReactQueryConfig = {
queries: {
queryKeySerializerFn: defaultQueryKeySerializerFn,
Expand All @@ -42,6 +60,63 @@ export const DEFAULT_CONFIG: ReactQueryConfig = {
},
}

export const defaultConfigRef: ReactQueryConfigRef = {
current: DEFAULT_CONFIG,
export function mergeReactQueryConfigs(
a: ReactQueryConfig,
b: ReactQueryConfig
): ReactQueryConfig {
return {
shared: {
...a.shared,
...b.shared,
},
queries: {
...a.queries,
...b.queries,
},
mutations: {
...a.mutations,
...b.mutations,
},
}
}

export function getDefaultedQueryConfig<TResult, TError>(
queryCacheConfig?: ReactQueryConfig,
contextConfig?: ReactQueryConfig,
config?: QueryConfig<TResult, TError>,
configOverrides?: QueryConfig<TResult, TError>
): QueryConfig<TResult, TError> {
return {
...DEFAULT_CONFIG.shared,
...DEFAULT_CONFIG.queries,
...queryCacheConfig?.shared,
...queryCacheConfig?.queries,
...contextConfig?.shared,
...contextConfig?.queries,
...config,
...configOverrides,
} as QueryConfig<TResult, TError>
}

export function getDefaultedMutationConfig<
TResult,
TError,
TVariables,
TSnapshot
>(
queryCacheConfig?: ReactQueryConfig,
contextConfig?: ReactQueryConfig,
config?: MutationConfig<TResult, TError, TVariables, TSnapshot>,
configOverrides?: MutationConfig<TResult, TError, TVariables, TSnapshot>
): MutationConfig<TResult, TError, TVariables, TSnapshot> {
return {
...DEFAULT_CONFIG.shared,
...DEFAULT_CONFIG.mutations,
...queryCacheConfig?.shared,
...queryCacheConfig?.mutations,
...contextConfig?.shared,
...contextConfig?.mutations,
...config,
...configOverrides,
} as MutationConfig<TResult, TError, TVariables, TSnapshot>
}
42 changes: 11 additions & 31 deletions src/core/queryCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
isObject,
Updater,
} from './utils'
import { defaultConfigRef, ReactQueryConfigRef } from './config'
import { getDefaultedQueryConfig } from './config'
import { Query } from './query'
import {
QueryConfig,
Expand Down Expand Up @@ -76,7 +76,6 @@ export class QueryCache {
isFetching: number

private config: QueryCacheConfig
private configRef: ReactQueryConfigRef
private globalListeners: QueryCacheListener[]

constructor(config?: QueryCacheConfig) {
Expand All @@ -85,25 +84,6 @@ export class QueryCache {
// A frozen cache does not add new queries to the cache
this.globalListeners = []

this.configRef = this.config.defaultConfig
? {
current: {
shared: {
...defaultConfigRef.current.shared,
...this.config.defaultConfig.shared,
},
queries: {
...defaultConfigRef.current.queries,
...this.config.defaultConfig.queries,
},
mutations: {
...defaultConfigRef.current.mutations,
...this.config.defaultConfig.mutations,
},
},
}
: defaultConfigRef

this.queries = {}
this.isFetching = 0
}
Expand All @@ -118,16 +98,15 @@ export class QueryCache {
}

getDefaultConfig() {
return this.configRef.current
return this.config.defaultConfig
}

getDefaultedConfig<TResult, TError>(config?: QueryConfig<TResult, TError>) {
return {
...this.configRef.current.shared!,
...this.configRef.current.queries!,
getDefaultedQueryConfig<TResult, TError>(
config?: QueryConfig<TResult, TError>
): QueryConfig<TResult, TError> {
return getDefaultedQueryConfig(this.getDefaultConfig(), undefined, config, {
queryCache: this,
...config,
} as QueryConfig<TResult, TError>
})
}

subscribe(listener: QueryCacheListener): () => void {
Expand Down Expand Up @@ -158,8 +137,8 @@ export class QueryCache {
if (typeof predicate === 'function') {
predicateFn = predicate as QueryPredicateFn
} else {
const [queryHash, queryKey] = this.configRef.current.queries!
.queryKeySerializerFn!(predicate)
const config = this.getDefaultedQueryConfig()
const [queryHash, queryKey] = config.queryKeySerializerFn!(predicate)

predicateFn = d =>
options?.exact
Expand Down Expand Up @@ -234,7 +213,7 @@ export class QueryCache {
userQueryKey: QueryKey,
queryConfig?: QueryConfig<TResult, TError>
): Query<TResult, TError> {
const config = this.getDefaultedConfig(queryConfig)
const config = this.getDefaultedQueryConfig(queryConfig)

const [queryHash, queryKey] = config.queryKeySerializerFn!(userQueryKey)

Expand Down Expand Up @@ -352,6 +331,7 @@ export class QueryCache {
TError,
PrefetchQueryOptions | undefined
>(args)

// https://github.com/tannerlinsley/react-query/issues/652
const configWithoutRetry = { retry: false, ...config }

Expand Down
4 changes: 4 additions & 0 deletions src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ export interface MutationConfig<
onMutate?: (variables: TVariables) => Promise<TSnapshot> | TSnapshot
useErrorBoundary?: boolean
suspense?: boolean
/**
* By default the query cache from the context is used, but a different cache can be specified.
*/
queryCache?: QueryCache
}

export type MutationFunction<TResult, TVariables = unknown> = (
Expand Down
64 changes: 12 additions & 52 deletions src/react/ReactQueryConfigProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,74 +1,34 @@
import React from 'react'
import { DEFAULT_CONFIG, defaultConfigRef } from '../core/config'

import { mergeReactQueryConfigs } from '../core/config'
import { ReactQueryConfig } from '../core/types'
import { useQueryCache } from './ReactQueryCacheProvider'

const configContext = React.createContext<ReactQueryConfig | undefined>(
undefined
)

export function useConfigContext() {
const queryCache = useQueryCache()
return (
React.useContext(configContext) ||
queryCache.getDefaultConfig() ||
defaultConfigRef.current
)
export function useContextConfig() {
return React.useContext(configContext)
}

export interface ReactQueryProviderConfig extends ReactQueryConfig {}

export interface ReactQueryConfigProviderProps {
config: ReactQueryProviderConfig
config: ReactQueryConfig
}

export const ReactQueryConfigProvider: React.FC<ReactQueryConfigProviderProps> = ({
config,
children,
}) => {
const configContextValueOrDefault = useConfigContext()
const configContextValue = React.useContext(configContext)
const parentConfig = useContextConfig()

const newConfig = React.useMemo<ReactQueryConfig>(() => {
const { shared = {}, queries = {}, mutations = {} } = config
const {
shared: contextShared = {},
queries: contextQueries = {},
mutations: contextMutations = {},
} = configContextValueOrDefault

return {
shared: {
...contextShared,
...shared,
},
queries: {
...contextQueries,
...queries,
},
mutations: {
...contextMutations,
...mutations,
},
}
}, [config, configContextValueOrDefault])

React.useEffect(() => {
// restore previous config on unmount
return () => {
defaultConfigRef.current = {
...(configContextValueOrDefault || DEFAULT_CONFIG),
}
}
}, [configContextValueOrDefault])

// If this is the outermost provider, overwrite the shared default config
if (!configContextValue) {
defaultConfigRef.current = newConfig
}
const mergedConfig = React.useMemo(
() =>
parentConfig ? mergeReactQueryConfigs(parentConfig, config) : config,
[config, parentConfig]
)

return (
<configContext.Provider value={newConfig}>
<configContext.Provider value={mergedConfig}>
{children}
</configContext.Provider>
)
Expand Down
5 changes: 1 addition & 4 deletions src/react/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,4 @@ export type { UseQueryObjectConfig } from './useQuery'
export type { UseInfiniteQueryObjectConfig } from './useInfiniteQuery'
export type { UsePaginatedQueryObjectConfig } from './usePaginatedQuery'
export type { ReactQueryCacheProviderProps } from './ReactQueryCacheProvider'
export type {
ReactQueryConfigProviderProps,
ReactQueryProviderConfig,
} from './ReactQueryConfigProvider'
export type { ReactQueryConfigProviderProps } from './ReactQueryConfigProvider'
26 changes: 15 additions & 11 deletions src/react/tests/ReactQueryConfigProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ describe('ReactQueryConfigProvider', () => {
})

it('should allow overriding the default config from the outermost provider', async () => {
const key = queryKey()
const key1 = queryKey()
const key2 = queryKey()

const outerConfig = {
queries: {
Expand All @@ -65,25 +66,28 @@ describe('ReactQueryConfigProvider', () => {
function Container() {
return (
<ReactQueryConfigProvider config={outerConfig}>
<First />
<ReactQueryConfigProvider config={innerConfig}>
<h1>Placeholder</h1>
<Second />
</ReactQueryConfigProvider>
</ReactQueryConfigProvider>
)
}

const rendered = render(<Container />)

await waitFor(() => rendered.getByText('Placeholder'))

await queryCache.prefetchQuery(key)
function First() {
const { data } = useQuery(key1)
return <span>First: {String(data)}</span>
}

expect(outerConfig.queries.queryFn).toHaveBeenCalledWith(key)
expect(innerConfig.queries.queryFn).not.toHaveBeenCalled()
function Second() {
const { data } = useQuery(key2)
return <span>Second: {String(data)}</span>
}

const data = queryCache.getQueryData(key)
const rendered = render(<Container />)

expect(data).toEqual('outer')
await waitFor(() => rendered.getByText('First: outer'))
await waitFor(() => rendered.getByText('Second: inner'))
})

it('should reset to defaults when unmounted', async () => {
Expand Down
4 changes: 2 additions & 2 deletions src/react/tests/useInfiniteQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,10 @@ describe('useInfiniteQuery', () => {
React.useEffect(() => {
setTimeout(() => {
fetchMore()
}, 20)
}, 50)
setTimeout(() => {
setOrder('asc')
}, 40)
}, 100)
}, [fetchMore])

return null
Expand Down
3 changes: 3 additions & 0 deletions src/react/useBaseQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import React from 'react'
import { useRerenderer } from './utils'
import { QueryObserver } from '../core/queryObserver'
import { QueryResultBase, QueryObserverConfig } from '../core/types'
import { useDefaultedQueryConfig } from './useDefaultedQueryConfig'

export function useBaseQuery<TResult, TError>(
config: QueryObserverConfig<TResult, TError> = {}
): QueryResultBase<TResult, TError> {
config = useDefaultedQueryConfig(config)

// Make a rerender function
const rerender = useRerenderer()

Expand Down
21 changes: 21 additions & 0 deletions src/react/useDefaultedMutationConfig.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MutationConfig } from '../core/types'
import { getDefaultedMutationConfig } from '../core/config'
import { useQueryCache } from './ReactQueryCacheProvider'
import { useContextConfig } from './ReactQueryConfigProvider'

export function useDefaultedMutationConfig<
TResult,
TError,
TVariables,
TSnapshot
>(
config?: MutationConfig<TResult, TError, TVariables, TSnapshot>
): MutationConfig<TResult, TError, TVariables, TSnapshot> {
const contextConfig = useContextConfig()
const contextQueryCache = useQueryCache()
const queryCache = config?.queryCache || contextQueryCache
const queryCacheConfig = queryCache.getDefaultConfig()
return getDefaultedMutationConfig(queryCacheConfig, contextConfig, config, {
queryCache,
})
}
Loading