Skip to content

Commit 4f80fd3

Browse files
committed
refactor: define configuration merging strategy
1 parent 58acca1 commit 4f80fd3

File tree

8 files changed

+137
-115
lines changed

8 files changed

+137
-115
lines changed

src/core/config.ts

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
QueryKey,
55
QueryKeySerializerFunction,
66
ReactQueryConfig,
7+
QueryConfig,
8+
MutationConfig,
79
} from './types'
810

911
// TYPES
@@ -29,6 +31,22 @@ export const defaultQueryKeySerializerFn: QueryKeySerializerFunction = (
2931
}
3032
}
3133

34+
/**
35+
* Config merging strategy
36+
*
37+
* When using hooks the config will be merged in the following order:
38+
*
39+
* 1. These defaults.
40+
* 2. Defaults from the hook query cache.
41+
* 3. Combined defaults from any config providers in the tree.
42+
* 4. Query/mutation config provided to the hook.
43+
*
44+
* When using a query cache directly the config will be merged in the following order:
45+
*
46+
* 1. These defaults.
47+
* 2. Defaults from the query cache.
48+
* 3. Query/mutation config provided to the query cache method.
49+
*/
3250
export const DEFAULT_CONFIG: ReactQueryConfig = {
3351
shared: {
3452
suspense: false,
@@ -53,6 +71,59 @@ export const DEFAULT_CONFIG: ReactQueryConfig = {
5371
},
5472
}
5573

56-
export const defaultConfigRef: ReactQueryConfigRef = {
57-
current: DEFAULT_CONFIG,
74+
export function mergeReactQueryConfigs(
75+
a: ReactQueryConfig,
76+
b: ReactQueryConfig
77+
): ReactQueryConfig {
78+
return {
79+
shared: {
80+
...a.shared,
81+
...b.shared,
82+
},
83+
queries: {
84+
...a.queries,
85+
...b.queries,
86+
},
87+
mutations: {
88+
...a.mutations,
89+
...b.mutations,
90+
},
91+
}
92+
}
93+
94+
export function getDefaultedQueryConfig<TResult, TError>(
95+
queryCacheConfig?: ReactQueryConfig,
96+
contextConfig?: ReactQueryConfig,
97+
config?: QueryConfig<TResult, TError>
98+
): QueryConfig<TResult, TError> {
99+
return {
100+
...DEFAULT_CONFIG.shared,
101+
...DEFAULT_CONFIG.queries,
102+
...queryCacheConfig?.shared,
103+
...queryCacheConfig?.queries,
104+
...contextConfig?.shared,
105+
...contextConfig?.queries,
106+
...config,
107+
} as QueryConfig<TResult, TError>
108+
}
109+
110+
export function getDefaultedMutationConfig<
111+
TResult,
112+
TError,
113+
TVariables,
114+
TSnapshot
115+
>(
116+
queryCacheConfig?: ReactQueryConfig,
117+
contextConfig?: ReactQueryConfig,
118+
config?: MutationConfig<TResult, TError, TVariables, TSnapshot>
119+
): MutationConfig<TResult, TError, TVariables, TSnapshot> {
120+
return {
121+
...DEFAULT_CONFIG.shared,
122+
...DEFAULT_CONFIG.mutations,
123+
...queryCacheConfig?.shared,
124+
...queryCacheConfig?.mutations,
125+
...contextConfig?.shared,
126+
...contextConfig?.mutations,
127+
...config,
128+
} as MutationConfig<TResult, TError, TVariables, TSnapshot>
58129
}

src/core/queryCache.ts

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
isObject,
77
Updater,
88
} from './utils'
9-
import { defaultConfigRef, ReactQueryConfigRef } from './config'
9+
import { getDefaultedQueryConfig } from './config'
1010
import { Query } from './query'
1111
import {
1212
QueryConfig,
@@ -76,7 +76,6 @@ export class QueryCache {
7676
isFetching: number
7777

7878
private config: QueryCacheConfig
79-
private configRef: ReactQueryConfigRef
8079
private globalListeners: QueryCacheListener[]
8180

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

88-
this.configRef = this.config.defaultConfig
89-
? {
90-
current: {
91-
shared: {
92-
...defaultConfigRef.current.shared,
93-
...this.config.defaultConfig.shared,
94-
},
95-
queries: {
96-
...defaultConfigRef.current.queries,
97-
...this.config.defaultConfig.queries,
98-
},
99-
mutations: {
100-
...defaultConfigRef.current.mutations,
101-
...this.config.defaultConfig.mutations,
102-
},
103-
},
104-
}
105-
: defaultConfigRef
106-
10787
this.queries = {}
10888
this.isFetching = 0
10989
}
@@ -118,7 +98,13 @@ export class QueryCache {
11898
}
11999

120100
getDefaultConfig() {
121-
return this.configRef.current
101+
return this.config.defaultConfig
102+
}
103+
104+
getDefaultedQueryConfig<TResult, TError>(
105+
config?: QueryConfig<TResult, TError>
106+
): QueryConfig<TResult, TError> {
107+
return getDefaultedQueryConfig(this.config.defaultConfig, undefined, config)
122108
}
123109

124110
subscribe(listener: QueryCacheListener): () => void {
@@ -149,8 +135,8 @@ export class QueryCache {
149135
if (typeof predicate === 'function') {
150136
predicateFn = predicate as QueryPredicateFn
151137
} else {
152-
const [queryHash, queryKey] = this.configRef.current.queries!
153-
.queryKeySerializerFn!(predicate)
138+
const config = this.getDefaultedQueryConfig()
139+
const [queryHash, queryKey] = config.queryKeySerializerFn!(predicate)
154140

155141
predicateFn = d =>
156142
options?.exact
@@ -226,13 +212,9 @@ export class QueryCache {
226212

227213
buildQuery<TResult, TError = unknown>(
228214
userQueryKey: QueryKey,
229-
queryConfig: QueryConfig<TResult, TError> = {}
215+
queryConfig?: QueryConfig<TResult, TError>
230216
): Query<TResult, TError> {
231-
const config = {
232-
...this.configRef.current.shared!,
233-
...this.configRef.current.queries!,
234-
...queryConfig,
235-
} as QueryConfig<TResult, TError>
217+
const config = this.getDefaultedQueryConfig(queryConfig)
236218

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

@@ -386,7 +368,7 @@ export class QueryCache {
386368
setQueryData<TResult, TError = unknown>(
387369
queryKey: QueryKey,
388370
updater: Updater<TResult | undefined, TResult>,
389-
config: QueryConfig<TResult, TError> = {}
371+
config?: QueryConfig<TResult, TError>
390372
) {
391373
let query = this.getQuery<TResult, TError>(queryKey)
392374

src/react/ReactQueryCacheProvider.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ export const queryCacheContext = React.createContext(defaultQueryCache)
1111

1212
export const useQueryCache = () => React.useContext(queryCacheContext)
1313

14+
export function useQueryCacheConfig() {
15+
return useQueryCache().getDefaultConfig()
16+
}
17+
1418
export interface ReactQueryCacheProviderProps {
1519
queryCache?: QueryCache
1620
}

src/react/ReactQueryConfigProvider.tsx

Lines changed: 12 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,34 @@
11
import React from 'react'
2-
import { DEFAULT_CONFIG, defaultConfigRef } from '../core/config'
2+
3+
import { mergeReactQueryConfigs } from '../core/config'
34
import { ReactQueryConfig } from '../core/types'
4-
import { useQueryCache } from './ReactQueryCacheProvider'
55

66
const configContext = React.createContext<ReactQueryConfig | undefined>(
77
undefined
88
)
99

10-
export function useConfigContext() {
11-
const queryCache = useQueryCache()
12-
return (
13-
React.useContext(configContext) ||
14-
queryCache.getDefaultConfig() ||
15-
defaultConfigRef.current
16-
)
10+
export function useContextConfig() {
11+
return React.useContext(configContext)
1712
}
1813

19-
export interface ReactQueryProviderConfig extends ReactQueryConfig {}
20-
2114
export interface ReactQueryConfigProviderProps {
22-
config: ReactQueryProviderConfig
15+
config: ReactQueryConfig
2316
}
2417

2518
export const ReactQueryConfigProvider: React.FC<ReactQueryConfigProviderProps> = ({
2619
config,
2720
children,
2821
}) => {
29-
const configContextValueOrDefault = useConfigContext()
30-
const configContextValue = React.useContext(configContext)
22+
const contextConfig = useContextConfig()
3123

32-
const newConfig = React.useMemo<ReactQueryConfig>(() => {
33-
const { shared = {}, queries = {}, mutations = {} } = config
34-
const {
35-
shared: contextShared = {},
36-
queries: contextQueries = {},
37-
mutations: contextMutations = {},
38-
} = configContextValueOrDefault
39-
40-
return {
41-
shared: {
42-
...contextShared,
43-
...shared,
44-
},
45-
queries: {
46-
...contextQueries,
47-
...queries,
48-
},
49-
mutations: {
50-
...contextMutations,
51-
...mutations,
52-
},
53-
}
54-
}, [config, configContextValueOrDefault])
55-
56-
React.useEffect(() => {
57-
// restore previous config on unmount
58-
return () => {
59-
defaultConfigRef.current = {
60-
...(configContextValueOrDefault || DEFAULT_CONFIG),
61-
}
62-
}
63-
}, [configContextValueOrDefault])
64-
65-
// If this is the outermost provider, overwrite the shared default config
66-
if (!configContextValue) {
67-
defaultConfigRef.current = newConfig
68-
}
24+
const combinedConfig = React.useMemo(
25+
() =>
26+
contextConfig ? mergeReactQueryConfigs(contextConfig, config) : config,
27+
[config, contextConfig]
28+
)
6929

7030
return (
71-
<configContext.Provider value={newConfig}>
31+
<configContext.Provider value={combinedConfig}>
7232
{children}
7333
</configContext.Provider>
7434
)

src/react/index.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,4 @@ export type { UseQueryObjectConfig } from './useQuery'
1717
export type { UseInfiniteQueryObjectConfig } from './useInfiniteQuery'
1818
export type { UsePaginatedQueryObjectConfig } from './usePaginatedQuery'
1919
export type { ReactQueryCacheProviderProps } from './ReactQueryCacheProvider'
20-
export type {
21-
ReactQueryConfigProviderProps,
22-
ReactQueryProviderConfig,
23-
} from './ReactQueryConfigProvider'
20+
export type { ReactQueryConfigProviderProps } from './ReactQueryConfigProvider'

src/react/tests/ReactQueryConfigProvider.test.tsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ describe('ReactQueryConfigProvider', () => {
4242
})
4343

4444
it('should allow overriding the default config from the outermost provider', async () => {
45-
const key = queryKey()
45+
const key1 = queryKey()
46+
const key2 = queryKey()
4647

4748
const outerConfig = {
4849
queries: {
@@ -65,25 +66,28 @@ describe('ReactQueryConfigProvider', () => {
6566
function Container() {
6667
return (
6768
<ReactQueryConfigProvider config={outerConfig}>
69+
<First />
6870
<ReactQueryConfigProvider config={innerConfig}>
69-
<h1>Placeholder</h1>
71+
<Second />
7072
</ReactQueryConfigProvider>
7173
</ReactQueryConfigProvider>
7274
)
7375
}
7476

75-
const rendered = render(<Container />)
76-
77-
await waitFor(() => rendered.getByText('Placeholder'))
78-
79-
await queryCache.prefetchQuery(key)
77+
function First() {
78+
const { data } = useQuery(key1)
79+
return <span>First: {String(data)}</span>
80+
}
8081

81-
expect(outerConfig.queries.queryFn).toHaveBeenCalledWith(key)
82-
expect(innerConfig.queries.queryFn).not.toHaveBeenCalled()
82+
function Second() {
83+
const { data } = useQuery(key2)
84+
return <span>Second: {String(data)}</span>
85+
}
8386

84-
const data = queryCache.getQueryData(key)
87+
const rendered = render(<Container />)
8588

86-
expect(data).toEqual('outer')
89+
await waitFor(() => rendered.getByText('First: outer'))
90+
await waitFor(() => rendered.getByText('Second: inner'))
8791
})
8892

8993
it('should reset to defaults when unmounted', async () => {

src/react/useMutation.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react'
22

3-
import { useConfigContext } from './ReactQueryConfigProvider'
3+
import { useContextConfig } from './ReactQueryConfigProvider'
44
import { useGetLatest, useMountedCallback } from './utils'
55
import { Console, uid, getStatusProps } from '../core/utils'
66
import {
@@ -10,6 +10,8 @@ import {
1010
MutationConfig,
1111
MutateConfig,
1212
} from '../core/types'
13+
import { useQueryCacheConfig } from './ReactQueryCacheProvider'
14+
import { getDefaultedMutationConfig } from '../core/config'
1315

1416
// TYPES
1517

@@ -113,13 +115,12 @@ export function useMutation<
113115

114116
const getMutationFn = useGetLatest(mutationFn)
115117

116-
const contextConfig = useConfigContext()
118+
const contextConfig = useContextConfig()
119+
const queryCacheConfig = useQueryCacheConfig()
117120

118-
const getConfig = useGetLatest({
119-
...contextConfig.shared,
120-
...contextConfig.mutations,
121-
...config,
122-
})
121+
const getConfig = useGetLatest(
122+
getDefaultedMutationConfig(queryCacheConfig, contextConfig, config)
123+
)
123124

124125
const latestMutationRef = React.useRef<number>()
125126

0 commit comments

Comments
 (0)