Skip to content

Commit 66777c6

Browse files
author
Jack Ellis
committed
feat: query cache provider
the query cache now comes from a context, meaning users can easily override and control the cache being used inside their application
1 parent ff5d6a7 commit 66777c6

16 files changed

+327
-76
lines changed

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,10 @@ This library is being built and maintained by me, @tannerlinsley and I am always
313313
- [`queryCache.isFetching`](#querycacheisfetching)
314314
- [`queryCache.subscribe`](#querycachesubscribe)
315315
- [`queryCache.clear`](#querycacheclear)
316+
- [`useQueryCache`](#usequerycache)
316317
- [`useIsFetching`](#useisfetching)
317318
- [`ReactQueryConfigProvider`](#reactqueryconfigprovider)
319+
- [`ReactQueryCacheProvider`](#reactquerycacheprovider)
318320
- [`setConsole`](#setconsole)
319321
- [Contributors ✨](#contributors-)
320322

@@ -2548,6 +2550,18 @@ queryCache.clear()
25482550
- `queries: Array<Query>`
25492551
- This will be an array containing the queries that were found.
25502552
2553+
## `useQueryCache`
2554+
2555+
The `useQueryCache` hook returns the current queryCache instance.
2556+
2557+
```js
2558+
import { useQueryCache } from 'react-query';
2559+
2560+
const queryCache = useQueryCache()
2561+
```
2562+
2563+
If you are using the `ReactQueryCacheProvider` to set a custom cache, you cannot simply import `{ queryCache }` any more. This hook will ensure you're getting the correct instance.
2564+
25512565
## `useIsFetching`
25522566
25532567
`useIsFetching` is an optional hook that returns the `number` of the queries that your application is loading or fetching in the background (useful for app-wide loading indicators).
@@ -2608,6 +2622,29 @@ function App() {
26082622
- Must be **stable** or **memoized**. Do not create an inline object!
26092623
- For non-global properties please see their usage in both the [`useQuery` hook](#usequery) and the [`useMutation` hook](#usemutation).
26102624
2625+
## `ReactQueryCacheProvider`
2626+
2627+
`ReactQueryCacheProvider` is an optional provider component for explicitly setting the query cache used by `useQuery`. This is useful for creating component-level caches that are not completely global, as well as making truly isolated unit tests.
2628+
2629+
```js
2630+
import { ReactQueryCacheProvider, makeQueryCache } from 'react-query';
2631+
2632+
const queryCache = makeQueryCache()
2633+
2634+
function App() {
2635+
return (
2636+
<ReactQueryCacheProvider queryCache={queryCache}>
2637+
...
2638+
</ReactQueryCacheProvider>
2639+
)
2640+
}
2641+
```
2642+
2643+
### Options
2644+
- `queryCache: Object`
2645+
- In instance of queryCache, you can use the `makeQueryCache` factory to create this.
2646+
- If not provided, a new cache will be generated.
2647+
26112648
## `setConsole`
26122649
26132650
`setConsole` is an optional utility function that allows you to replace the `console` interface used to log errors. By default, the `window.console` object is used. If no global `console` object is found in the environment, nothing will be logged.

src/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
export { queryCache } from './queryCache'
1+
export {
2+
queryCache,
3+
makeQueryCache,
4+
ReactQueryCacheProvider,
5+
useQueryCache,
6+
} from './queryCache'
27
export { ReactQueryConfigProvider } from './config'
38
export { setFocusHandler } from './setFocusHandler'
49
export { useIsFetching } from './useIsFetching'

src/index.production.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
export { queryCache } from './queryCache'
1+
export {
2+
queryCache,
3+
makeQueryCache,
4+
ReactQueryCacheProvider,
5+
useQueryCache,
6+
} from './queryCache'
27
export { ReactQueryConfigProvider } from './config'
38
export { setFocusHandler } from './setFocusHandler'
49
export { useIsFetching } from './useIsFetching'

src/queryCache.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import React from 'react'
12
import {
23
isServer,
34
functionalUpdate,
@@ -14,6 +15,25 @@ import { defaultConfigRef } from './config'
1415

1516
export const queryCache = makeQueryCache()
1617

18+
export const defaultQueryCacheRef = {
19+
current: queryCache
20+
}
21+
22+
export const queryCacheContext = React.createContext(queryCache)
23+
24+
export function useQueryCache() {
25+
return React.useContext(queryCacheContext)
26+
}
27+
28+
export function ReactQueryCacheProvider({ queryCache, children }) {
29+
defaultQueryCacheRef.current = queryCache || makeQueryCache()
30+
return (
31+
<queryCacheContext.Provider value={defaultQueryCacheRef.current}>
32+
{children}
33+
</queryCacheContext.Provider>
34+
)
35+
}
36+
1737
const actionInit = {}
1838
const actionFailed = {}
1939
const actionMarkStale = {}
@@ -32,7 +52,7 @@ export function makeQueryCache() {
3252
}
3353

3454
const notifyGlobalListeners = () => {
35-
cache.isFetching = Object.values(queryCache.queries).reduce(
55+
cache.isFetching = Object.values(cache.queries).reduce(
3656
(acc, query) => (query.state.isFetching ? acc + 1 : acc),
3757
0
3858
)
@@ -113,6 +133,7 @@ export function makeQueryCache() {
113133
query.config = { ...query.config, ...config }
114134
} else {
115135
query = makeQuery({
136+
cache,
116137
queryKey,
117138
queryHash,
118139
queryVariables,
@@ -196,6 +217,7 @@ export function makeQueryCache() {
196217
}
197218

198219
function makeQuery(options) {
220+
const queryCache = options.cache
199221
const reducer = options.config.queryReducer || defaultQueryReducer
200222

201223
const noQueryHash = typeof options.queryHash === 'undefined'

src/setFocusHandler.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { isOnline, isDocumentVisible, Console, isServer } from './utils'
22
import { defaultConfigRef } from './config'
3-
import { queryCache } from './queryCache'
3+
import { defaultQueryCacheRef } from './queryCache'
44

55
const visibilityChangeEvent = 'visibilitychange'
66
const focusEvent = 'focus'
@@ -9,7 +9,7 @@ const onWindowFocus = () => {
99
const { refetchAllOnWindowFocus } = defaultConfigRef.current
1010

1111
if (isDocumentVisible() && isOnline()) {
12-
queryCache
12+
defaultQueryCacheRef.current
1313
.refetchQueries(query => {
1414
if (!query.instances.length) {
1515
return false

src/tests/config.test.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import React from 'react'
22
import { render, waitForElement, cleanup } from '@testing-library/react'
3-
import { ReactQueryConfigProvider, useQuery, queryCache } from '../index'
3+
import { ReactQueryConfigProvider, useQuery, ReactQueryCacheProvider } from '../index'
44

55
import { sleep } from './utils'
66

77
describe('config', () => {
88
afterEach(() => {
9-
queryCache.clear()
109
cleanup()
1110
})
1211

@@ -33,7 +32,9 @@ describe('config', () => {
3332

3433
const rendered = render(
3534
<ReactQueryConfigProvider config={config}>
36-
<Page />
35+
<ReactQueryCacheProvider>
36+
<Page />
37+
</ReactQueryCacheProvider>
3738
</ReactQueryConfigProvider>
3839
)
3940

src/tests/queryCache.test.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
import { queryCache } from '../index'
2-
import { act } from '@testing-library/react'
1+
import { makeQueryCache } from '../index'
32
import { sleep } from './utils'
43

54
describe('queryCache', () => {
6-
afterEach(() => {
7-
queryCache.clear()
8-
})
95

106
test('setQueryData does not crash if query could not be found', () => {
7+
const queryCache = makeQueryCache();
118
expect(() =>
129
queryCache.setQueryData(['USER', { userId: 1 }], prevUser => ({
1310
...prevUser,
@@ -17,6 +14,7 @@ describe('queryCache', () => {
1714
})
1815

1916
test('setQueryData does not crash when variable is null', () => {
17+
const queryCache = makeQueryCache();
2018
queryCache.setQueryData(['USER', { userId: null }], 'Old Data')
2119

2220
expect(() =>
@@ -25,6 +23,7 @@ describe('queryCache', () => {
2523
})
2624

2725
test('prefetchQuery returns the cached data on cache hits', async () => {
26+
const queryCache = makeQueryCache();
2827
const fetchFn = () => Promise.resolve('data')
2928
const first = await queryCache.prefetchQuery('key', fetchFn)
3029
const second = await queryCache.prefetchQuery('key', fetchFn)
@@ -33,6 +32,7 @@ describe('queryCache', () => {
3332
})
3433

3534
test('prefetchQuery should force fetch', async () => {
35+
const queryCache = makeQueryCache();
3636
const fetchFn = () => Promise.resolve('fresh')
3737
const first = await queryCache.prefetchQuery('key', fetchFn, {
3838
initialData: 'initial',
@@ -43,6 +43,7 @@ describe('queryCache', () => {
4343
})
4444

4545
test('prefetchQuery should throw error when throwOnError is true', async () => {
46+
const queryCache = makeQueryCache();
4647
const fetchFn = () =>
4748
new Promise(() => {
4849
throw new Error('error')
@@ -57,6 +58,7 @@ describe('queryCache', () => {
5758
})
5859

5960
test('should notify listeners when new query is added', () => {
61+
const queryCache = makeQueryCache();
6062
const callback = jest.fn()
6163

6264
queryCache.subscribe(callback)
@@ -67,6 +69,7 @@ describe('queryCache', () => {
6769
})
6870

6971
test('should notify subsribers when new query with initialData is added', () => {
72+
const queryCache = makeQueryCache();
7073
const callback = jest.fn()
7174

7275
queryCache.subscribe(callback)
@@ -77,18 +80,21 @@ describe('queryCache', () => {
7780
})
7881

7982
test('setQueryData creates a new query if query was not found, using exact', () => {
83+
const queryCache = makeQueryCache();
8084
queryCache.setQueryData('foo', 'bar', { exact: true })
8185

8286
expect(queryCache.getQueryData('foo')).toBe('bar')
8387
})
8488

8589
test('setQueryData creates a new query if query was not found', () => {
90+
const queryCache = makeQueryCache();
8691
queryCache.setQueryData('baz', 'qux')
8792

8893
expect(queryCache.getQueryData('baz')).toBe('qux')
8994
})
9095

9196
test('removeQueries does not crash when exact is provided', async () => {
97+
const queryCache = makeQueryCache();
9298
const fetchFn = () => Promise.resolve('data')
9399

94100
// check the query was added to the cache
@@ -103,6 +109,7 @@ describe('queryCache', () => {
103109
})
104110

105111
test('setQueryData schedules stale timeouts appropriately', async () => {
112+
const queryCache = makeQueryCache();
106113
queryCache.setQueryData('key', 'test data', { staleTime: 100 })
107114

108115
expect(queryCache.getQuery('key').state.data).toEqual('test data')
@@ -118,6 +125,7 @@ describe('queryCache', () => {
118125
})
119126

120127
test('setQueryData updater function works as expected', () => {
128+
const queryCache = makeQueryCache();
121129
const updater = jest.fn(oldData => `new data + ${oldData}`)
122130

123131
queryCache.setQueryData('updater', 'test data')
@@ -130,6 +138,7 @@ describe('queryCache', () => {
130138
})
131139

132140
test('getQueries should return queries that partially match queryKey', async () => {
141+
const queryCache = makeQueryCache();
133142
const fetchData1 = () => Promise.resolve('data1')
134143
const fetchData2 = () => Promise.resolve('data2')
135144
const fetchDifferentData = () => Promise.resolve('data3')
@@ -142,6 +151,7 @@ describe('queryCache', () => {
142151
})
143152

144153
test('stale timeout dispatch is not called if query is no longer in the query cache', async () => {
154+
const queryCache = makeQueryCache();
145155
const queryKey = 'key'
146156
const fetchData = () => Promise.resolve('data')
147157
await queryCache.prefetchQuery(queryKey, fetchData)
@@ -153,6 +163,7 @@ describe('queryCache', () => {
153163
})
154164

155165
test('query is garbage collected when unsubscribed to', async () => {
166+
const queryCache = makeQueryCache();
156167
const queryKey = 'key'
157168
const fetchData = () => Promise.resolve('data')
158169
await queryCache.prefetchQuery(queryKey, fetchData, { cacheTime: 0 })
@@ -166,6 +177,7 @@ describe('queryCache', () => {
166177
})
167178

168179
test('query is not garbage collected unless markedForGarbageCollection is true', async () => {
180+
const queryCache = makeQueryCache();
169181
const queryKey = 'key'
170182
const fetchData = () => Promise.resolve(undefined)
171183
await queryCache.prefetchQuery(queryKey, fetchData, { cacheTime: 0 })

src/tests/suspense.test.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import { render, waitForElement, cleanup } from '@testing-library/react'
22
import * as React from 'react'
33

4-
import { useQuery, queryCache } from '../index'
4+
import { useQuery, ReactQueryCacheProvider } from '../index'
55
import { sleep } from './utils'
66

77
describe("useQuery's in Suspense mode", () => {
88
afterEach(() => {
9-
queryCache.clear()
109
cleanup()
1110
})
1211

@@ -21,9 +20,11 @@ describe("useQuery's in Suspense mode", () => {
2120
}
2221

2322
const rendered = render(
24-
<React.Suspense fallback="loading">
25-
<Page />
26-
</React.Suspense>
23+
<ReactQueryCacheProvider>
24+
<React.Suspense fallback="loading">
25+
<Page />
26+
</React.Suspense>
27+
</ReactQueryCacheProvider>
2728
)
2829

2930
await waitForElement(() => rendered.getByText('rendered'))
@@ -44,9 +45,11 @@ describe("useQuery's in Suspense mode", () => {
4445
}
4546

4647
const rendered = render(
47-
<React.Suspense fallback="loading">
48-
<Page />
49-
</React.Suspense>
48+
<ReactQueryCacheProvider>
49+
<React.Suspense fallback="loading">
50+
<Page />
51+
</React.Suspense>
52+
</ReactQueryCacheProvider>
5053
)
5154

5255
await waitForElement(() => rendered.getByText('rendered'))

0 commit comments

Comments
 (0)