Skip to content

Commit 3b2d35f

Browse files
committed
refactor: define configuration merging strategy
1 parent 58acca1 commit 3b2d35f

File tree

7 files changed

+135
-108
lines changed

7 files changed

+135
-108
lines changed

src/core/config.ts

Lines changed: 75 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,61 @@ 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 b
79+
? {
80+
shared: {
81+
...a.shared,
82+
...b.shared,
83+
},
84+
queries: {
85+
...a.queries,
86+
...b.queries,
87+
},
88+
mutations: {
89+
...a.mutations,
90+
...b.mutations,
91+
},
92+
}
93+
: a
94+
}
95+
96+
export function getDefaultedQueryConfig<TResult, TError>(
97+
queryCacheConfig?: ReactQueryConfig,
98+
contextConfig?: ReactQueryConfig,
99+
config?: QueryConfig<TResult, TError>
100+
): QueryConfig<TResult, TError> {
101+
return {
102+
...DEFAULT_CONFIG.shared,
103+
...DEFAULT_CONFIG.queries,
104+
...queryCacheConfig?.shared,
105+
...queryCacheConfig?.queries,
106+
...contextConfig?.shared,
107+
...contextConfig?.queries,
108+
...config,
109+
} as QueryConfig<TResult, TError>
110+
}
111+
112+
export function getDefaultedMutationConfig<
113+
TResult,
114+
TError,
115+
TVariables,
116+
TSnapshot
117+
>(
118+
queryCacheConfig?: ReactQueryConfig,
119+
contextConfig?: ReactQueryConfig,
120+
config?: MutationConfig<TResult, TError, TVariables, TSnapshot>
121+
): MutationConfig<TResult, TError, TVariables, TSnapshot> {
122+
return {
123+
...DEFAULT_CONFIG.shared,
124+
...DEFAULT_CONFIG.mutations,
125+
...queryCacheConfig?.shared,
126+
...queryCacheConfig?.mutations,
127+
...contextConfig?.shared,
128+
...contextConfig?.mutations,
129+
...config,
130+
} as MutationConfig<TResult, TError, TVariables, TSnapshot>
58131
}

src/core/queryCache.ts

Lines changed: 11 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,7 @@ export class QueryCache {
11898
}
11999

120100
getDefaultConfig() {
121-
return this.configRef.current
101+
return this.config.defaultConfig
122102
}
123103

124104
subscribe(listener: QueryCacheListener): () => void {
@@ -149,8 +129,8 @@ export class QueryCache {
149129
if (typeof predicate === 'function') {
150130
predicateFn = predicate as QueryPredicateFn
151131
} else {
152-
const [queryHash, queryKey] = this.configRef.current.queries!
153-
.queryKeySerializerFn!(predicate)
132+
const config = getDefaultedQueryConfig(this.config.defaultConfig)
133+
const [queryHash, queryKey] = config!.queryKeySerializerFn!(predicate)
154134

155135
predicateFn = d =>
156136
options?.exact
@@ -226,13 +206,13 @@ export class QueryCache {
226206

227207
buildQuery<TResult, TError = unknown>(
228208
userQueryKey: QueryKey,
229-
queryConfig: QueryConfig<TResult, TError> = {}
209+
queryConfig?: QueryConfig<TResult, TError>
230210
): Query<TResult, TError> {
231-
const config = {
232-
...this.configRef.current.shared!,
233-
...this.configRef.current.queries!,
234-
...queryConfig,
235-
} as QueryConfig<TResult, TError>
211+
const config = getDefaultedQueryConfig(
212+
this.getDefaultConfig(),
213+
undefined,
214+
queryConfig
215+
)
236216

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

@@ -386,7 +366,7 @@ export class QueryCache {
386366
setQueryData<TResult, TError = unknown>(
387367
queryKey: QueryKey,
388368
updater: Updater<TResult | undefined, TResult>,
389-
config: QueryConfig<TResult, TError> = {}
369+
config?: QueryConfig<TResult, TError>
390370
) {
391371
let query = this.getQuery<TResult, TError>(queryKey)
392372

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: 11 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
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

1914
export interface ReactQueryProviderConfig extends ReactQueryConfig {}
@@ -26,49 +21,16 @@ export const ReactQueryConfigProvider: React.FC<ReactQueryConfigProviderProps> =
2621
config,
2722
children,
2823
}) => {
29-
const configContextValueOrDefault = useConfigContext()
30-
const configContextValue = React.useContext(configContext)
31-
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
24+
const contextConfig = useContextConfig()
3925

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-
}
26+
const combinedConfig = React.useMemo(
27+
() =>
28+
contextConfig ? mergeReactQueryConfigs(contextConfig, config) : config,
29+
[config, contextConfig]
30+
)
6931

7032
return (
71-
<configContext.Provider value={newConfig}>
33+
<configContext.Provider value={combinedConfig}>
7234
{children}
7335
</configContext.Provider>
7436
)

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

src/react/useQueryArgs.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
import { getQueryArgs } from '../core/utils'
2-
import { useConfigContext } from './ReactQueryConfigProvider'
2+
import { useContextConfig } from './ReactQueryConfigProvider'
33
import { QueryConfig, QueryKey } from '../core/types'
4+
import { useQueryCacheConfig } from './ReactQueryCacheProvider'
5+
import { getDefaultedQueryConfig } from '../core/config'
46

57
export function useQueryArgs<TResult, TError, TOptions = undefined>(
68
args: any[]
79
): [QueryKey, QueryConfig<TResult, TError>, TOptions] {
8-
const configContext = useConfigContext()
10+
const contextConfig = useContextConfig()
11+
const queryCacheConfig = useQueryCacheConfig()
912

1013
const [queryKey, config, options] = getQueryArgs<TResult, TError, TOptions>(
1114
args
1215
)
1316

1417
// Build the final config
15-
const configWithContext = {
16-
...configContext.shared,
17-
...configContext.queries,
18-
...config,
19-
} as QueryConfig<TResult, TError>
18+
const finalConfig = getDefaultedQueryConfig(
19+
queryCacheConfig,
20+
contextConfig,
21+
config
22+
)
2023

21-
return [queryKey, configWithContext, options]
24+
return [queryKey, finalConfig, options]
2225
}

0 commit comments

Comments
 (0)