From 11456a8f6d818a4a4b1c672a996710cbd0b59b4d Mon Sep 17 00:00:00 2001 From: "Henry Q. Dineen" Date: Thu, 14 May 2020 00:05:21 -0400 Subject: [PATCH 1/5] chore(rollup): run terser plugin for react-query.min.mjs bundle (#488) --- rollup.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rollup.config.js b/rollup.config.js index b46a6d6808..d87d7883e9 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -32,7 +32,7 @@ export default [ sourcemap: true, }, external, - plugins: [resolve(), babel(), commonJS(), externalDeps()], + plugins: [resolve(), babel(), commonJS(), externalDeps(), terser()], }, { input: 'src/index.js', From 99fd14540c91978c8823679ac11cf27cb2e488dc Mon Sep 17 00:00:00 2001 From: "Henry Q. Dineen" Date: Thu, 14 May 2020 00:10:05 -0400 Subject: [PATCH 2/5] perf(babelrc): enable externalHelpers option for babel-plugin-transform-async-to-promises (#490) --- .babelrc | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.babelrc b/.babelrc index 7fde532a6b..e9cbf6a63b 100644 --- a/.babelrc +++ b/.babelrc @@ -10,7 +10,14 @@ ], "@babel/react" ], - "plugins": ["babel-plugin-transform-async-to-promises"], + "plugins": [ + [ + "babel-plugin-transform-async-to-promises", + { + externalHelpers: true + } + ] + ], "env": { "test": { "presets": [ @@ -22,7 +29,8 @@ "exclude": ["@babel/plugin-transform-regenerator"] } ] - ] + ], + "plugins": ["babel-plugin-transform-async-to-promises"] } } } From f6715959161e5d146855906c8a004d7493061d52 Mon Sep 17 00:00:00 2001 From: "Henry Q. Dineen" Date: Thu, 14 May 2020 00:11:05 -0400 Subject: [PATCH 3/5] docs(CONTRIBUTING): fix "chnages" typo (#491) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fc194bcd78..12f7ca6dde 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ If you have been assigned to fix an issue or develop a new feature, please follo - To run examples, follow their individual directions. Usually this is just `$ yarn && yarn start`. - To run examples using your local build, link to the local `react-query` by running `$ yarn link react-query` from the example's directory - Document your changes in the appropriate doc page -- Git stage your required chnages and commit (see below commit guidelines) +- Git stage your required changes and commit (see below commit guidelines) - Submit PR for review ## Commit message conventions From 588d54cfc54d3c62e486d8f6cdfd12eed98c36b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Nunes?= Date: Fri, 15 May 2020 01:00:43 +0100 Subject: [PATCH 4/5] Add cancelQueries types and missing links in docs (#494) * fix(Types): add queryCache.cancelQueries types * docs: add missing links to cancelQueries --- README.md | 8 +++++--- types/index.d.ts | 7 +++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7003140d3d..9cdcd15933 100644 --- a/README.md +++ b/README.md @@ -250,6 +250,7 @@ This library is being built and maintained by me, @tannerlinsley and I am always - [`queryCache.getQueryData`](#querycachegetquerydata) - [`queryCache.setQueryData`](#querycachesetquerydata) - [`queryCache.refetchQueries`](#querycacherefetchqueries) + - [`queryCache.cancelQueries`](#querycachecancelqueries) - [`queryCache.removeQueries`](#querycacheremovequeries) - [`queryCache.getQuery`](#querycachegetquery) - [`queryCache.getQueries`](#querycachegetqueries) @@ -2236,6 +2237,7 @@ The `queryCache` instance is the backbone of React Query that manages all of the - [`getQueryData`](#querycachegetquerydata) - [`setQueryData`](#querycachesetquerydata) - [`refetchQueries`](#querycacherefetchqueries) +- [`cancelQueries`](#querycachecancelqueries) - [`removeQueries`](#querycacheremovequeries) - [`getQueries`](#querycachegetqueries) - [`getQuery`](#querycachegetquery) @@ -2361,7 +2363,7 @@ const queries = queryCache.refetchQueries(inclusiveQueryKeyOrPredicateFn, { - This predicate function will be called for every single query in the cache and be expected to return truthy for queries that are `found`. - The `exact` option has no effect with using a function - `exact: Boolean` - - If you don't want to search queries inclusively by query key, you can pass the `exact: true` option to return only the query with the exact query key you have passed. Don't remember to destructure it out of the array! + - If you don't want to search queries inclusively by query key, you can pass the `exact: true` option to return only the query with the exact query key you have passed. Remember to destructure it out of the array! - `throwOnError: Boolean` - When set to `true`, this function will throw if any of the query refetch tasks fail. - `force: Boolean` @@ -2394,7 +2396,7 @@ const queries = queryCache.cancelQueries(queryKeyOrPredicateFn, { - This predicate function will be called for every single query in the cache and be expected to return truthy for queries that are `found`. - The `exact` option has no effect with using a function - `exact: Boolean` - - If you don't want to search queries inclusively by query key, you can pass the `exact: true` option to return only the query with the exact query key you have passed. Don't remember to destructure it out of the array! + - If you don't want to search queries inclusively by query key, you can pass the `exact: true` option to return only the query with the exact query key you have passed. Remember to destructure it out of the array! ### Returns @@ -2421,7 +2423,7 @@ const queries = queryCache.removeQueries(queryKeyOrPredicateFn, { - This predicate function will be called for every single query in the cache and be expected to return truthy for queries that are `found`. - The `exact` option has no effect with using a function - `exact: Boolean` - - If you don't want to search queries inclusively by query key, you can pass the `exact: true` option to return only the query with the exact query key you have passed. Don't remember to destructure it out of the array! + - If you don't want to search queries inclusively by query key, you can pass the `exact: true` option to return only the query with the exact query key you have passed. Remember to destructure it out of the array! ### Returns diff --git a/types/index.d.ts b/types/index.d.ts index cb23e40c44..24497fda19 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -692,6 +692,13 @@ export interface QueryCache { ): Promise getQuery(queryKey: AnyQueryKey): CachedQuery | undefined getQueries(queryKey: AnyQueryKey): Array> + cancelQueries( + queryKeyOrPredicateFn: + | AnyQueryKey + | string + | ((query: CachedQuery) => boolean), + { exact }?: { exact?: boolean } + ): void isFetching: number subscribe(callback: (queryCache: QueryCache) => void): () => void clear(): Array> From 51375fdfb7dfaa06e8b52caf98fd0e460c6e0ad2 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 15 May 2020 22:04:17 +0100 Subject: [PATCH 5/5] feat: query cache provider (#476) * 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 * fix: track multiple query caches this commit does the follwing: track all active query caches added via the provider, handles mounting/unmounting if creating a cache, ensure we only do it once when unmounting an internally-created cache, make sure to clear it first setFocusHandler now loops through all of the currently-active caches * test: add in-depth tests around query cache providers * style: format touched files * fixup! style: format touched files * docs: correct readme description Co-authored-by: Tanner Linsley Co-authored-by: Jack Ellis Co-authored-by: Tanner Linsley --- README.md | 37 ++++ src/index.js | 7 +- src/index.production.js | 7 +- src/queryCache.js | 41 ++++- src/setFocusHandler.js | 50 +++--- src/tests/ReactQueryCacheProvider.test.js | 177 +++++++++++++++++++ src/tests/config.test.js | 11 +- src/tests/queryCache.test.js | 23 ++- src/tests/suspense.test.js | 19 +- src/tests/useInfiniteQuery.test.js | 26 ++- src/tests/useIsFetching.test.js | 9 +- src/tests/usePaginatedQuery.test.js | 21 ++- src/tests/useQuery-SSR.test.js | 17 +- src/tests/useQuery.test.js | 201 ++++++++++++++++++---- src/useBaseQuery.js | 4 +- src/useIsFetching.js | 3 +- types/index.d.ts | 14 ++ 17 files changed, 568 insertions(+), 99 deletions(-) create mode 100644 src/tests/ReactQueryCacheProvider.test.js diff --git a/README.md b/README.md index 9cdcd15933..498d6a8e99 100644 --- a/README.md +++ b/README.md @@ -257,8 +257,10 @@ This library is being built and maintained by me, @tannerlinsley and I am always - [`queryCache.isFetching`](#querycacheisfetching) - [`queryCache.subscribe`](#querycachesubscribe) - [`queryCache.clear`](#querycacheclear) + - [`useQueryCache`](#usequerycache) - [`useIsFetching`](#useisfetching) - [`ReactQueryConfigProvider`](#reactqueryconfigprovider) + - [`ReactQueryCacheProvider`](#reactquerycacheprovider) - [`setConsole`](#setconsole) - [Contributors ✨](#contributors-) @@ -2524,6 +2526,18 @@ queryCache.clear() - `queries: Array` - This will be an array containing the queries that were found. +## `useQueryCache` + +The `useQueryCache` hook returns the current queryCache instance. + +```js +import { useQueryCache } from 'react-query'; + +const queryCache = useQueryCache() +``` + +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. + ## `useIsFetching` `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). @@ -2584,6 +2598,29 @@ function App() { - Must be **stable** or **memoized**. Do not create an inline object! - For non-global properties please see their usage in both the [`useQuery` hook](#usequery) and the [`useMutation` hook](#usemutation). +## `ReactQueryCacheProvider` + +`ReactQueryCacheProvider` is an optional provider component for explicitly setting the query cache used by React Query. This is useful for creating component-level caches that are not completely global, as well as making truly isolated unit tests. + +```js +import { ReactQueryCacheProvider, makeQueryCache } from 'react-query'; + +const queryCache = makeQueryCache() + +function App() { + return ( + + ... + + ) +} +``` + +### Options +- `queryCache: Object` + - In instance of queryCache, you can use the `makeQueryCache` factory to create this. + - If not provided, a new cache will be generated. + ## `setConsole` `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. diff --git a/src/index.js b/src/index.js index ce451b3e8c..31e0138df6 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,9 @@ -export { queryCache } from './queryCache' +export { + queryCache, + makeQueryCache, + ReactQueryCacheProvider, + useQueryCache, +} from './queryCache' export { ReactQueryConfigProvider } from './config' export { setFocusHandler } from './setFocusHandler' export { useIsFetching } from './useIsFetching' diff --git a/src/index.production.js b/src/index.production.js index ce451b3e8c..31e0138df6 100644 --- a/src/index.production.js +++ b/src/index.production.js @@ -1,4 +1,9 @@ -export { queryCache } from './queryCache' +export { + queryCache, + makeQueryCache, + ReactQueryCacheProvider, + useQueryCache, +} from './queryCache' export { ReactQueryConfigProvider } from './config' export { setFocusHandler } from './setFocusHandler' export { useIsFetching } from './useIsFetching' diff --git a/src/queryCache.js b/src/queryCache.js index 677795cc3a..37d9ee4c96 100644 --- a/src/queryCache.js +++ b/src/queryCache.js @@ -1,3 +1,4 @@ +import React from 'react' import { isServer, functionalUpdate, @@ -14,6 +15,42 @@ import { defaultConfigRef } from './config' export const queryCache = makeQueryCache() +export const queryCacheContext = React.createContext(queryCache) + +export const queryCaches = [queryCache] + +export function useQueryCache() { + return React.useContext(queryCacheContext) +} + +export function ReactQueryCacheProvider({ queryCache, children }) { + const cache = React.useMemo(() => queryCache || makeQueryCache(), [ + queryCache, + ]) + + React.useEffect(() => { + queryCaches.push(cache) + + return () => { + // remove the cache from the active list + const i = queryCaches.indexOf(cache) + if (i >= 0) { + queryCaches.splice(i, 1) + } + // if the cache was created by us, we need to tear it down + if (queryCache == null) { + cache.clear() + } + } + }, [cache, queryCache]) + + return ( + + {children} + + ) +} + const actionInit = {} const actionFailed = {} const actionMarkStale = {} @@ -32,7 +69,7 @@ export function makeQueryCache() { } const notifyGlobalListeners = () => { - cache.isFetching = Object.values(queryCache.queries).reduce( + cache.isFetching = Object.values(cache.queries).reduce( (acc, query) => (query.state.isFetching ? acc + 1 : acc), 0 ) @@ -129,6 +166,7 @@ export function makeQueryCache() { query.config = { ...query.config, ...config } } else { query = makeQuery({ + cache, queryKey, queryHash, queryVariables, @@ -212,6 +250,7 @@ export function makeQueryCache() { } function makeQuery(options) { + const queryCache = options.cache const reducer = options.config.queryReducer || defaultQueryReducer const noQueryHash = typeof options.queryHash === 'undefined' diff --git a/src/setFocusHandler.js b/src/setFocusHandler.js index fce8666fba..774adbfcb3 100644 --- a/src/setFocusHandler.js +++ b/src/setFocusHandler.js @@ -1,6 +1,6 @@ import { isOnline, isDocumentVisible, Console, isServer } from './utils' import { defaultConfigRef } from './config' -import { queryCache } from './queryCache' +import { queryCaches } from './queryCache' const visibilityChangeEvent = 'visibilitychange' const focusEvent = 'focus' @@ -9,29 +9,31 @@ const onWindowFocus = () => { const { refetchAllOnWindowFocus } = defaultConfigRef.current if (isDocumentVisible() && isOnline()) { - queryCache - .refetchQueries(query => { - if (!query.instances.length) { - return false - } - - if (query.config.manual === true) { - return false - } - - if (query.shouldContinueRetryOnFocus) { - // delete promise, so `fetch` will create new one - delete query.promise - return true - } - - if (typeof query.config.refetchOnWindowFocus === 'undefined') { - return refetchAllOnWindowFocus - } else { - return query.config.refetchOnWindowFocus - } - }) - .catch(Console.error) + queryCaches.forEach(queryCache => + queryCache + .refetchQueries(query => { + if (!query.instances.length) { + return false + } + + if (query.config.manual === true) { + return false + } + + if (query.shouldContinueRetryOnFocus) { + // delete promise, so `fetch` will create new one + delete query.promise + return true + } + + if (typeof query.config.refetchOnWindowFocus === 'undefined') { + return refetchAllOnWindowFocus + } else { + return query.config.refetchOnWindowFocus + } + }) + .catch(Console.error) + ) } } diff --git a/src/tests/ReactQueryCacheProvider.test.js b/src/tests/ReactQueryCacheProvider.test.js new file mode 100644 index 0000000000..c652e089d9 --- /dev/null +++ b/src/tests/ReactQueryCacheProvider.test.js @@ -0,0 +1,177 @@ +import React, { useEffect } from 'react' +import { render, cleanup, waitForElement } from '@testing-library/react' +import { + ReactQueryCacheProvider, + makeQueryCache, + queryCache, + useQuery, + useQueryCache, +} from '../index' +import { sleep } from './utils' + +describe('ReactQueryCacheProvider', () => { + afterEach(() => { + cleanup() + queryCache.clear() + }) + + test('when not used, falls back to global cache', async () => { + function Page() { + const { data } = useQuery('test', async () => { + await sleep(10) + return 'test' + }) + + return ( +
+

{data}

+
+ ) + } + + const rendered = render() + + await waitForElement(() => rendered.getByText('test')) + + expect(queryCache.getQuery('test')).toBeDefined() + }) + test('sets a specific cache for all queries to use', async () => { + const cache = makeQueryCache() + + function Page() { + const { data } = useQuery('test', async () => { + await sleep(10) + return 'test' + }) + + return ( +
+

{data}

+
+ ) + } + + const rendered = render( + + + + ) + + await waitForElement(() => rendered.getByText('test')) + + expect(queryCache.getQuery('test')).not.toBeDefined() + expect(cache.getQuery('test')).toBeDefined() + }) + test('implicitly creates a new cache for all queries to use', async () => { + function Page() { + const { data } = useQuery('test', async () => { + await sleep(10) + return 'test' + }) + + return ( +
+

{data}

+
+ ) + } + + const rendered = render( + + + + ) + + await waitForElement(() => rendered.getByText('test')) + + expect(queryCache.getQuery('test')).not.toBeDefined() + }) + test('allows multiple caches to be partitioned', async () => { + const cache1 = makeQueryCache() + const cache2 = makeQueryCache() + + function Page1() { + const { data } = useQuery('test1', async () => { + await sleep(10) + return 'test1' + }) + + return ( +
+

{data}

+
+ ) + } + function Page2() { + const { data } = useQuery('test2', async () => { + await sleep(10) + return 'test2' + }) + + return ( +
+

{data}

+
+ ) + } + + const rendered = render( + <> + + + + + + + + ) + + await waitForElement(() => rendered.getByText('test1')) + await waitForElement(() => rendered.getByText('test2')) + + expect(cache1.getQuery('test1')).toBeDefined() + expect(cache1.getQuery('test2')).not.toBeDefined() + expect(cache2.getQuery('test1')).not.toBeDefined() + expect(cache2.getQuery('test2')).toBeDefined() + }) + test('when cache changes, previous cache is cleaned', () => { + let caches = [] + const customCache = makeQueryCache() + + function Page() { + const queryCache = useQueryCache() + useEffect(() => { + caches.push(queryCache) + }, [queryCache]) + + const { data } = useQuery('test', async () => { + await sleep(10) + return 'test' + }) + + return ( +
+

{data}

+
+ ) + } + + function App({ cache }) { + return ( + + + + ) + } + + const rendered = render() + + expect(caches).toHaveLength(1) + jest.spyOn(caches[0], 'clear') + + rendered.rerender() + + expect(caches).toHaveLength(2) + expect(caches[0].clear).toHaveBeenCalled() + }) +}) diff --git a/src/tests/config.test.js b/src/tests/config.test.js index b15cbb3dce..6eb76930f9 100644 --- a/src/tests/config.test.js +++ b/src/tests/config.test.js @@ -1,12 +1,15 @@ import React from 'react' import { render, waitForElement, cleanup } from '@testing-library/react' -import { ReactQueryConfigProvider, useQuery, queryCache } from '../index' +import { + ReactQueryConfigProvider, + useQuery, + ReactQueryCacheProvider, +} from '../index' import { sleep } from './utils' describe('config', () => { afterEach(() => { - queryCache.clear() cleanup() }) @@ -33,7 +36,9 @@ describe('config', () => { const rendered = render( - + + + ) diff --git a/src/tests/queryCache.test.js b/src/tests/queryCache.test.js index 9413d23d85..81b9de3233 100644 --- a/src/tests/queryCache.test.js +++ b/src/tests/queryCache.test.js @@ -1,13 +1,9 @@ -import { queryCache } from '../index' -import { act } from '@testing-library/react' +import { makeQueryCache } from '../index' import { sleep } from './utils' describe('queryCache', () => { - afterEach(() => { - queryCache.clear() - }) - test('setQueryData does not crash if query could not be found', () => { + const queryCache = makeQueryCache() expect(() => queryCache.setQueryData(['USER', { userId: 1 }], prevUser => ({ ...prevUser, @@ -17,6 +13,7 @@ describe('queryCache', () => { }) test('setQueryData does not crash when variable is null', () => { + const queryCache = makeQueryCache() queryCache.setQueryData(['USER', { userId: null }], 'Old Data') expect(() => @@ -25,6 +22,7 @@ describe('queryCache', () => { }) test('prefetchQuery returns the cached data on cache hits', async () => { + const queryCache = makeQueryCache() const fetchFn = () => Promise.resolve('data') const first = await queryCache.prefetchQuery('key', fetchFn) const second = await queryCache.prefetchQuery('key', fetchFn) @@ -33,6 +31,7 @@ describe('queryCache', () => { }) test('prefetchQuery should force fetch', async () => { + const queryCache = makeQueryCache() const fetchFn = () => Promise.resolve('fresh') const first = await queryCache.prefetchQuery('key', fetchFn, { initialData: 'initial', @@ -43,6 +42,7 @@ describe('queryCache', () => { }) test('prefetchQuery should throw error when throwOnError is true', async () => { + const queryCache = makeQueryCache() const fetchFn = () => new Promise(() => { throw new Error('error') @@ -57,6 +57,7 @@ describe('queryCache', () => { }) test('should notify listeners when new query is added', () => { + const queryCache = makeQueryCache() const callback = jest.fn() queryCache.subscribe(callback) @@ -67,6 +68,7 @@ describe('queryCache', () => { }) test('should notify subsribers when new query with initialData is added', () => { + const queryCache = makeQueryCache() const callback = jest.fn() queryCache.subscribe(callback) @@ -77,18 +79,21 @@ describe('queryCache', () => { }) test('setQueryData creates a new query if query was not found, using exact', () => { + const queryCache = makeQueryCache() queryCache.setQueryData('foo', 'bar', { exact: true }) expect(queryCache.getQueryData('foo')).toBe('bar') }) test('setQueryData creates a new query if query was not found', () => { + const queryCache = makeQueryCache() queryCache.setQueryData('baz', 'qux') expect(queryCache.getQueryData('baz')).toBe('qux') }) test('removeQueries does not crash when exact is provided', async () => { + const queryCache = makeQueryCache() const fetchFn = () => Promise.resolve('data') // check the query was added to the cache @@ -103,6 +108,7 @@ describe('queryCache', () => { }) test('setQueryData schedules stale timeouts appropriately', async () => { + const queryCache = makeQueryCache() queryCache.setQueryData('key', 'test data', { staleTime: 100 }) expect(queryCache.getQuery('key').state.data).toEqual('test data') @@ -118,6 +124,7 @@ describe('queryCache', () => { }) test('setQueryData updater function works as expected', () => { + const queryCache = makeQueryCache() const updater = jest.fn(oldData => `new data + ${oldData}`) queryCache.setQueryData('updater', 'test data') @@ -130,6 +137,7 @@ describe('queryCache', () => { }) test('getQueries should return queries that partially match queryKey', async () => { + const queryCache = makeQueryCache() const fetchData1 = () => Promise.resolve('data1') const fetchData2 = () => Promise.resolve('data2') const fetchDifferentData = () => Promise.resolve('data3') @@ -142,6 +150,7 @@ describe('queryCache', () => { }) test('stale timeout dispatch is not called if query is no longer in the query cache', async () => { + const queryCache = makeQueryCache() const queryKey = 'key' const fetchData = () => Promise.resolve('data') await queryCache.prefetchQuery(queryKey, fetchData) @@ -153,6 +162,7 @@ describe('queryCache', () => { }) test('query is garbage collected when unsubscribed to', async () => { + const queryCache = makeQueryCache() const queryKey = 'key' const fetchData = () => Promise.resolve('data') await queryCache.prefetchQuery(queryKey, fetchData, { cacheTime: 0 }) @@ -166,6 +176,7 @@ describe('queryCache', () => { }) test('query is not garbage collected unless markedForGarbageCollection is true', async () => { + const queryCache = makeQueryCache() const queryKey = 'key' const fetchData = () => Promise.resolve(undefined) await queryCache.prefetchQuery(queryKey, fetchData, { cacheTime: 0 }) diff --git a/src/tests/suspense.test.js b/src/tests/suspense.test.js index 527b36ce2b..e3904a10fb 100644 --- a/src/tests/suspense.test.js +++ b/src/tests/suspense.test.js @@ -1,12 +1,11 @@ import { render, waitForElement, fireEvent, cleanup } from '@testing-library/react' import * as React from 'react' -import { useQuery, queryCache } from '../index' +import { useQuery, ReactQueryCacheProvider, queryCache } from '../index' import { sleep } from './utils' describe("useQuery's in Suspense mode", () => { afterEach(() => { - queryCache.clear() cleanup() }) @@ -21,9 +20,11 @@ describe("useQuery's in Suspense mode", () => { } const rendered = render( - - - + + + + + ) await waitForElement(() => rendered.getByText('rendered')) @@ -80,9 +81,11 @@ describe("useQuery's in Suspense mode", () => { } const rendered = render( - - - + + + + + ) await waitForElement(() => rendered.getByText('rendered')) diff --git a/src/tests/useInfiniteQuery.test.js b/src/tests/useInfiniteQuery.test.js index a38e19c92d..c6c524e794 100644 --- a/src/tests/useInfiniteQuery.test.js +++ b/src/tests/useInfiniteQuery.test.js @@ -6,7 +6,11 @@ import { } from '@testing-library/react' import * as React from 'react' -import { useInfiniteQuery, queryCache } from '../index' +import { + useInfiniteQuery, + ReactQueryCacheProvider, + useQueryCache, +} from '../index' import { sleep } from './utils' const pageSize = 10 @@ -30,7 +34,6 @@ const fetchItems = async (page, ts) => { describe('useInfiniteQuery', () => { afterEach(() => { - queryCache.clear() cleanup() }) @@ -100,7 +103,11 @@ describe('useInfiniteQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) rendered.getByText('Loading...') @@ -196,7 +203,11 @@ describe('useInfiniteQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) rendered.getByText('Item: 9') rendered.getByText('Page 0: 0') @@ -235,6 +246,7 @@ describe('useInfiniteQuery', () => { } function Page() { + const queryCache = useQueryCache() const fetchCountRef = React.useRef(0) const { status, @@ -305,7 +317,11 @@ describe('useInfiniteQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) rendered.getByText('Loading...') diff --git a/src/tests/useIsFetching.test.js b/src/tests/useIsFetching.test.js index f5d5623c4c..aa58db1e79 100644 --- a/src/tests/useIsFetching.test.js +++ b/src/tests/useIsFetching.test.js @@ -6,13 +6,12 @@ import { } from '@testing-library/react' import * as React from 'react' -import { useQuery, queryCache, useIsFetching } from '../index' +import { useQuery, ReactQueryCacheProvider, useIsFetching } from '../index' import { sleep } from './utils' describe('useIsFetching', () => { afterEach(() => { cleanup() - queryCache.clear() }) // See https://github.com/tannerlinsley/react-query/issues/105 @@ -35,7 +34,11 @@ describe('useIsFetching', () => { ) } - const rendered = render() + const rendered = render( + + + + ) rendered.getByText('isFetching: 0') fireEvent.click(rendered.getByText('setReady')) diff --git a/src/tests/usePaginatedQuery.test.js b/src/tests/usePaginatedQuery.test.js index a5f05e3306..91afda3f57 100644 --- a/src/tests/usePaginatedQuery.test.js +++ b/src/tests/usePaginatedQuery.test.js @@ -7,12 +7,11 @@ import { import * as React from 'react' import { sleep } from './utils' -import { usePaginatedQuery, queryCache } from '../index' +import { usePaginatedQuery, ReactQueryCacheProvider } from '../index' describe('usePaginatedQuery', () => { afterEach(() => { cleanup() - queryCache.clear() }) it('should use previous page data while fetching the next page', async () => { @@ -34,7 +33,11 @@ describe('usePaginatedQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) rendered.getByText('Data undefined') await waitForElement(() => rendered.getByText('Data 1')) @@ -69,7 +72,11 @@ describe('usePaginatedQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) rendered.getByText('Data 0') @@ -109,7 +116,11 @@ describe('usePaginatedQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) rendered.getByText('Data 0') diff --git a/src/tests/useQuery-SSR.test.js b/src/tests/useQuery-SSR.test.js index ea768d96c9..346e92c87f 100644 --- a/src/tests/useQuery-SSR.test.js +++ b/src/tests/useQuery-SSR.test.js @@ -4,15 +4,16 @@ import React from 'react' import { renderToString } from 'react-dom/server' -import { usePaginatedQuery, queryCache } from '../index' +import { + usePaginatedQuery, + ReactQueryCacheProvider, + makeQueryCache, +} from '../index' describe('useQuery SSR', () => { - afterEach(() => { - queryCache.clear() - }) - // See https://github.com/tannerlinsley/react-query/issues/70 it('should not cache queries on server', async () => { + const queryCache = makeQueryCache() function Page() { const [page, setPage] = React.useState(1) const { resolvedData } = usePaginatedQuery( @@ -31,7 +32,11 @@ describe('useQuery SSR', () => { ) } - renderToString() + renderToString( + + + + ) expect(queryCache.queries).toEqual({}) }) diff --git a/src/tests/useQuery.test.js b/src/tests/useQuery.test.js index 2cd95d879f..659980ce0e 100644 --- a/src/tests/useQuery.test.js +++ b/src/tests/useQuery.test.js @@ -1,13 +1,18 @@ -import { render, act, waitForElement, fireEvent } from '@testing-library/react' +import { render, act, waitForElement, fireEvent, cleanup } from '@testing-library/react' import { ErrorBoundary } from 'react-error-boundary' import * as React from 'react' -import { useQuery, queryCache } from '../index' +import { + useQuery, + useQueryCache, + makeQueryCache, + ReactQueryCacheProvider, +} from '../index' import { sleep } from './utils' describe('useQuery', () => { afterEach(() => { - queryCache.clear() + cleanup() }) // See https://github.com/tannerlinsley/react-query/issues/105 @@ -25,7 +30,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) rendered.getByText('default') await waitForElement(() => rendered.getByText('test')) @@ -52,7 +61,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) rendered.getByText('First Data: init') rendered.getByText('Second Data: init') @@ -74,7 +87,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) // use "act" to wait for state update and prevent console warning @@ -97,7 +114,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) rendered.getByText('First Status: success') await waitForElement(() => rendered.getByText('Second Status: loading')) @@ -119,7 +140,11 @@ describe('useQuery', () => { ) } - const { getByTestId } = render() + const { getByTestId } = render( + + + + ) await waitForElement(() => getByTestId('status')) act(() => { @@ -148,7 +173,11 @@ describe('useQuery', () => { ) } - const { findByText } = render() + const { findByText } = render( + + + + ) await findByText('success') }) @@ -166,7 +195,11 @@ describe('useQuery', () => { return null } - render() + render( + + + + ) expect(queryFn).toHaveBeenCalledWith('test', variables) }) @@ -185,7 +218,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) await waitForElement(() => rendered.getByText('default')) expect(queryFn).not.toHaveBeenCalled() }) @@ -203,7 +240,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) await waitForElement(() => rendered.getByText('default')) act(() => { @@ -231,7 +272,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) await waitForElement(() => rendered.getByText('error')) await waitForElement(() => rendered.getByText('Error test')) @@ -257,7 +302,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) await waitForElement(() => rendered.getByText('loading')) await waitForElement(() => rendered.getByText('error')) @@ -294,7 +343,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) await waitForElement(() => rendered.getByText('loading')) await waitForElement(() => rendered.getByText('error')) @@ -306,6 +359,7 @@ describe('useQuery', () => { }) it('should garbage collect queries without data immediately', async () => { + const queryCache = makeQueryCache() function Page() { const [filter, setFilter] = React.useState('') const { data } = useQuery( @@ -324,7 +378,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) await waitForElement(() => rendered.getByText('update')) @@ -368,7 +426,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) await waitForElement(() => rendered.getByText('failureCount 1')) await waitForElement(() => rendered.getByText('status loading')) @@ -385,6 +447,7 @@ describe('useQuery', () => { // See https://github.com/tannerlinsley/react-query/issues/195 it('should not refetch immediately after a prefetch', async () => { + const queryCache = makeQueryCache() const queryFn = jest.fn() queryFn.mockImplementation(() => sleep(10)) @@ -404,7 +467,11 @@ describe('useQuery', () => { await queryCache.prefetchQuery('test', prefetchQueryFn) await queryCache.prefetchQuery('test', prefetchQueryFn) - const rendered = render() + const rendered = render( + + + + ) await waitForElement(() => rendered.getByText('status success')) @@ -436,7 +503,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) await waitForElement(() => rendered.getByText('failureCount 1')) await waitForElement(() => rendered.getByText('failureCount 0')) @@ -445,6 +516,7 @@ describe('useQuery', () => { // See https://github.com/tannerlinsley/react-query/issues/199 it('should use prefetched data for dependent query', async () => { function Page() { + const queryCache = useQueryCache() const [key, setKey] = React.useState(false) const [isPrefetched, setPrefetched] = React.useState(false) @@ -458,7 +530,7 @@ describe('useQuery', () => { setPrefetched(true) } prefetch() - }, []) + }, [queryCache]) return (
@@ -469,7 +541,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) await waitForElement(() => rendered.getByText('isPrefetched')) fireEvent.click(rendered.getByText('setKey')) @@ -492,7 +568,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) rendered.getByText('Status: loading') await waitForElement(() => rendered.getByText('Status: success')) @@ -520,7 +600,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) rendered.getByText('Status: success') rendered.getByText('Data: no data') @@ -554,7 +638,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) rendered.getByText('Status: success') rendered.getByText('Data: no data') @@ -580,7 +668,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) rendered.getByText('data') expect(rendered.getByTestId('isFetching').textContent).toBe('false') @@ -599,7 +691,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) rendered.getByText('0') expect(rendered.getByTestId('isFetching').textContent).toBe('false') @@ -632,7 +728,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) await waitForElement(() => rendered.getByText('initial')) fireEvent.click(rendered.getByText('setShouldFetch(false)')) @@ -644,6 +744,7 @@ describe('useQuery', () => { it('it should support falsy queryKey in query object syntax', async () => { const queryFn = jest.fn() queryFn.mockImplementation(() => 'data') + const queryCache = makeQueryCache() function Page() { useQuery({ @@ -652,7 +753,11 @@ describe('useQuery', () => { }) return null } - render() + render( + + + + ) const cachedQueries = Object.keys(queryCache.queries).length expect(queryFn).not.toHaveBeenCalled() @@ -668,7 +773,13 @@ describe('useQuery', () => { useQuery({}) return null } - expect(() => render()).toThrowError(/queryKey|queryFn/) + expect(() => + render( + + + + ) + ).toThrowError(/queryKey|queryFn/) console.error.mockRestore() }) @@ -686,7 +797,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) await waitForElement(() => rendered.getByText('fetched data')) expect(rendered.getByTestId('isStale').textContent).toBe('true') @@ -710,7 +825,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) await waitForElement(() => rendered.getByText('fetched data')) expect(rendered.getByTestId('isStale').textContent).toBe('false') @@ -729,7 +848,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) await waitForElement(() => rendered.getByText('isFetching === false')) }) @@ -742,7 +865,13 @@ describe('useQuery', () => { return
{query.data}
} - const rendered = render() + const queryCache = makeQueryCache() + + const rendered = render( + + + + ) await waitForElement(() => rendered.getByText('fetched data')) @@ -791,7 +920,11 @@ describe('useQuery', () => { ) } - const rendered = render() + const rendered = render( + + + + ) await waitForElement(() => rendered.getByText('status loading')) await waitForElement(() => rendered.getByText('status success')) diff --git a/src/useBaseQuery.js b/src/useBaseQuery.js index 683fc3b4c1..7f58847d5e 100644 --- a/src/useBaseQuery.js +++ b/src/useBaseQuery.js @@ -2,7 +2,7 @@ import React from 'react' // -import { queryCache } from './queryCache' +import { useQueryCache } from './queryCache' import { useConfigContext } from './config' import { useUid, @@ -20,6 +20,8 @@ export function useBaseQuery(queryKey, queryVariables, queryFn, config = {}) { ...config, } + const queryCache = useQueryCache() + const queryRef = React.useRef() const newQuery = queryCache._buildQuery( diff --git a/src/useIsFetching.js b/src/useIsFetching.js index e053f29a4e..3c7a45d933 100644 --- a/src/useIsFetching.js +++ b/src/useIsFetching.js @@ -1,8 +1,9 @@ import React from 'react' -import { queryCache } from './queryCache' +import { useQueryCache } from './queryCache' export function useIsFetching() { + const queryCache = useQueryCache() const [state, setState] = React.useState({}) React.useEffect(() => { diff --git a/types/index.d.ts b/types/index.d.ts index 24497fda19..5a70804d89 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -706,6 +706,20 @@ export interface QueryCache { export const queryCache: QueryCache +/** + * a factory that creates a new query cache + */ +export function makeQueryCache(): QueryCache + +/** + * A hook that uses the query cache context + */ +export function useQueryCache(): QueryCache + +export const ReactQueryCacheProvider: React.ComponentType<{ + queryCache?: QueryCache +}> + /** * A hook that returns the number of the quiries that your application is loading or fetching in the background * (useful for app-wide loading indicators).