Repository: tannerlinsley/react-query
Pull request #1016 (2020-09-15 15:44:40)

fix: should throw error when using useErrorBoundary
Fixes https://github.com/tannerlinsley/react-query/issues/1015

vercel[bot]: This pull request is being automatically deployed with Vercel ([learn more](https://vercel.link/github-learn-more)).  
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: [https://vercel.com/tannerlinsley/react-query/d5svo858g](https://vercel.com/tannerlinsley/react-query/d5svo858g)  
✅ Preview: *In Progress*



Patch:
diff --git a/docs/src/pages/docs/api.md b/docs/src/pages/docs/api.md
index 2efec1dfe..39237b634 100644
--- a/docs/src/pages/docs/api.md
+++ b/docs/src/pages/docs/api.md
@@ -7,7 +7,6 @@ title: API Reference
 
 ```js
 const {
-  clear,
   data,
   error,
   failureCount,
@@ -20,6 +19,7 @@ const {
   isStale,
   isSuccess,
   refetch,
+  remove,
   status,
 } = useQuery(queryKey, queryFn?, {
   cacheTime,
@@ -194,7 +194,7 @@ const queryInfo = useQuery({
 - `refetch: Function({ throwOnError }) => Promise<TResult | undefined>`
   - A function to manually refetch the query.
   - If the query errors, the error will only be logged. If you want an error to be thrown, pass the `throwOnError: true` option
-- `clear: Function() => void`
+- `remove: Function() => void`
   - A function to remove the query from the cache.
 
 ## `usePaginatedQuery`
@@ -344,36 +344,41 @@ const promise = mutate(variables, {
 - `reset: Function() => void`
   - A function to clean the mutation internal state (i.e., it resets the mutation to its initial state).
 
-## `queryCache`
-
-The `queryCache` instance is the backbone of React Query that manages all of the state, caching, lifecycle and magic of every query. It supports relatively unrestricted, but safe, access to manipulate query's as you need. Its available properties and methods are:
-
-- [`useQuery`](#usequery)
-- [`usePaginatedQuery`](#usepaginatedquery)
-- [`useInfiniteQuery`](#useinfinitequery)
-- [`useMutation`](#usemutation)
-- [`queryCache`](#querycache)
-- [`queryCache.prefetchQuery`](#querycacheprefetchquery)
-- [`queryCache.getQueryData`](#querycachegetquerydata)
-- [`queryCache.setQueryData`](#querycachesetquerydata)
-- [`queryCache.invalidateQueries`](#querycacheinvalidatequeries)
-- [`queryCache.cancelQueries`](#querycachecancelqueries)
-- [`queryCache.removeQueries`](#querycacheremovequeries)
-- [`queryCache.getQuery`](#querycachegetquery)
-- [`queryCache.getQueries`](#querycachegetqueries)
-- [`queryCache.isFetching`](#querycacheisfetching)
-- [`queryCache.subscribe`](#querycachesubscribe)
-- [`queryCache.clear`](#querycacheclear)
-- [`makeQueryCache`](#makequerycache)
-- [`useQueryCache`](#usequerycache)
-- [`useIsFetching`](#useisfetching)
-- [`ReactQueryConfigProvider`](#reactqueryconfigprovider)
-- [`ReactQueryCacheProvider`](#reactquerycacheprovider)
-- [`setConsole`](#setconsole)
-- [`hydration/dehydrate`](#hydrationdehydrate)
-- [`hydration/hydrate`](#hydrationhydrate)
-- [`hydration/useHydrate`](#hydrationusehydrate)
-- [`hydration/ReactQueryCacheProvider`](#hydrationreactquerycacheprovider)
+## `QueryCache`
+
+The `QueryCache` is the backbone of React Query that manages all of the state, caching, lifecycle and magic of every query. It supports relatively unrestricted, but safe, access to manipulate query's as you need.
+
+```js
+import { QueryCache } from 'react-query'
+
+const queryCache = new QueryCache({
+  defaultConfig: {
+    queries: {
+      staleTime: Infinity,
+    },
+  },
+})
+```
+
+Its available properties and methods are:
+
+- [`prefetchQuery`](#querycacheprefetchquery)
+- [`getQueryData`](#querycachegetquerydata)
+- [`setQueryData`](#querycachesetquerydata)
+- [`invalidateQueries`](#querycacheinvalidatequeries)
+- [`cancelQueries`](#querycachecancelqueries)
+- [`removeQueries`](#querycacheremovequeries)
+- [`getQuery`](#querycachegetquery)
+- [`getQueries`](#querycachegetqueries)
+- [`isFetching`](#querycacheisfetching)
+- [`subscribe`](#querycachesubscribe)
+- [`clear`](#querycacheclear)
+
+**Options**
+
+- `defaultConfig: QueryQueryConfig`
+  - Optional
+  - Define defaults for all queries and mutations using this query cache.
 
 ## `queryCache.prefetchQuery`
 
@@ -426,8 +431,6 @@ The options for `prefetchQuery` are exactly the same as those of [`useQuery`](#u
 `getQueryData` is a synchronous function that can be used to get an existing query's cached data. If the query does not exist, `undefined` will be returned.
 
 ```js
-import { queryCache } from 'react-query'
-
 const data = queryCache.getQueryData(queryKey)
 ```
 
@@ -448,8 +451,6 @@ const data = queryCache.getQueryData(queryKey)
 > The difference between using `setQueryData` and `prefetchQuery` is that `setQueryData` is sync and assumes that you already synchronously have the data available. If you need to fetch the data asynchronously, it's suggested that you either refetch the query key or use `prefetchQuery` to handle the asynchronous fetch.
 
 ```js
-import { queryCache } from 'react-query'
-
 queryCache.setQueryData(queryKey, updater, config)
 ```
 
@@ -485,8 +486,6 @@ The `invalidateQueries` method can be used to invalidate and refetch single or m
 - If you **want inactive queries to refetch** as well, use the `refetchInactive: true` option
 
 ```js
-import { queryCache } from 'react-query'
-
 const queries = queryCache.invalidateQueries(inclusiveQueryKeyOrPredicateFn, {
   exact,
   throwOnError,
@@ -525,8 +524,6 @@ The `cancelQueries` method can be used to cancel outgoing queries based on their
 This is most useful when performing optimistic updates since you will likely need to cancel any outgoing query refetches so they don't clobber your optimistic update when they resolve.
 
 ```js
-import { queryCache } from 'react-query'
-
 const queries = queryCache.cancelQueries(queryKeyOrPredicateFn, {
   exact,
 })
@@ -552,8 +549,6 @@ This function does not return anything
 The `removeQueries` method can be used to remove queries from the cache based on their query keys or any other functionally accessible property/state of the query.
 
 ```js
-import { queryCache } from 'react-query'
-
 const queries = queryCache.removeQueries(queryKeyOrPredicateFn, {
   exact,
 })
@@ -581,8 +576,6 @@ This function does not return anything
 > Note: This is not typically needed for most applications, but can come in handy when needing more information about a query in rare scenarios (eg. Looking at the query.state.updatedAt timestamp to decide whether a query is fresh enough to be used as an initial value)
 
 ```js
-import { queryCache } from 'react-query'
-
 const query = queryCache.getQuery(queryKey)
 ```
 
@@ -603,8 +596,6 @@ const query = queryCache.getQuery(queryKey)
 > Note: This is not typically needed for most applications, but can come in handy when needing more information about a query in rare scenarios
 
 ```js
-import { queryCache } from 'react-query'
-
 const queries = queryCache.getQueries(queryKey)
 ```
 
@@ -623,8 +614,6 @@ const queries = queryCache.getQueries(queryKey)
 This `isFetching` property is an `integer` representing how many queries, if any, in the cache are currently fetching (including background-fetching, loading new pages, or loading more infinite query results)
 
 ```js
-import { queryCache } from 'react-query'
-
 if (queryCache.isFetching) {
   console.log('At least one query is fetching!')
 }
@@ -637,8 +626,6 @@ React Query also exports a handy [`useIsFetching`](#useisfetching) hook that wil
 The `subscribe` method can be used to subscribe to the query cache as a whole and be informed of safe/known updates to the cache like query states changing or queries being updated, added or removed
 
 ```js
-import { queryCache } from 'react-query'
-
 const callback = (cache, query) => {}
 
 const unsubscribe = queryCache.subscribe(callback)
@@ -660,8 +647,6 @@ const unsubscribe = queryCache.subscribe(callback)
 The `clear` method can be used to clear the queryCache entirely and start fresh.
 
 ```js
-import { queryCache } from 'react-query'
-
 queryCache.clear()
 ```
 
@@ -672,20 +657,7 @@ queryCache.clear()
 
 ## `makeQueryCache`
 
-`makeQueryCache` creates an empty `queryCache` manually. This is useful together with `ReactQueryCacheProvider` to have multiple caches in your application.
-
-As opposed to the global cache, caches created by `makeQueryCache` caches data even on the server.
-
-```js
-import { makeQueryCache } from 'react-query'
-
-const queryCache = makeQueryCache()
-```
-
-**Returns**
-
-- `queryCache: QueryCache`
-  - An empty `queryCache`
+The `makeQueryCache` factory function has been deprecated in favor of `new QueryCache()`.
 
 ## `useQueryCache`
 
@@ -697,8 +669,6 @@ 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).
@@ -722,11 +692,8 @@ const isFetching = useIsFetching()
 import { ReactQueryConfigProvider } from 'react-query'
 
 const queryConfig = {
-  shared: {
-    suspense: false,
-  },
   queries: {
-    suspense, // defaults to `shared.suspense`
+    suspense: false,
     queryKeySerializerFn: defaultQueryKeySerializerFn,
     queryFn,
     enabled: true,
@@ -745,7 +712,7 @@ const queryConfig = {
     useErrorBoundary: false, // falls back to suspense
   },
   mutations: {
-    suspense, // defaults to `shared.suspense`
+    suspense: false,
     throwOnError: false,
     onMutate: noop,
     onError: noop,
@@ -772,12 +739,12 @@ function App() {
 
 ## `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.
+The query cache can be connected to React with the `ReactQueryCacheProvider`. This component puts the cache on the context, which enables you to access it from anywhere in your component tree.
 
 ```js
-import { ReactQueryCacheProvider, makeQueryCache } from 'react-query'
+import { ReactQueryCacheProvider, QueryCache } from 'react-query'
 
-const queryCache = makeQueryCache()
+const queryCache = new QueryCache()
 
 function App() {
   return (
@@ -791,8 +758,7 @@ function App() {
 **Options**
 
 - `queryCache: QueryCache`
-  - In instance of queryCache, you can use the `makeQueryCache` factory to create this.
-  - If not provided, a new cache will be generated.
+  - Instance of QueryCache.
 
 ## `ReactQueryErrorResetBoundary`
 
diff --git a/docs/src/pages/docs/guides/invalidations-from-mutations.md b/docs/src/pages/docs/guides/invalidations-from-mutations.md
index 3e538bb94..a63a8844d 100644
--- a/docs/src/pages/docs/guides/invalidations-from-mutations.md
+++ b/docs/src/pages/docs/guides/invalidations-from-mutations.md
@@ -14,7 +14,9 @@ const [mutate] = useMutation(postTodo)
 When a successful `postTodo` mutation happens, we likely want all `todos` queries to get invalidated and possibly refetched to show the new todo item. To do this, you can use `useMutation`'s `onSuccess` options and the `queryCache`'s `invalidateQueries` function:
 
 ```js
-import { useMutation, queryCache } from 'react-query'
+import { useMutation, useQueryCache } from 'react-query'
+
+const queryCache = useQueryCache()
 
 // When this mutation succeeds, invalidate any queries with the `todos` or `reminders` query key
 const [mutate] = useMutation(addTodo, {
diff --git a/docs/src/pages/docs/guides/prefetching.md b/docs/src/pages/docs/guides/prefetching.md
index ea6bed5a9..2a868f341 100644
--- a/docs/src/pages/docs/guides/prefetching.md
+++ b/docs/src/pages/docs/guides/prefetching.md
@@ -6,8 +6,6 @@ title: Prefetching
 If you're lucky enough, you may know enough about what your users will do to be able to prefetch the data they need before it's needed! If this is the case, you can use the `prefetchQuery` function to prefetch the results of a query to be placed into the cache:
 
 ```js
-import { queryCache } from 'react-query'
-
 const prefetchTodos = async () => {
   const queryData = await queryCache.prefetchQuery('todos', () =>
     fetch('/todos')
@@ -25,7 +23,5 @@ If a prefetched query is rendered after the `staleTime` for a prefetched query,
 Alternatively, if you already have the data for your query synchronously available, you don't need to prefetch it. You can just use the [Query Cache's `setQueryData` method](../api/#querycachesetquerydata) to directly add or update a query's cached result.
 
 ```js
-import { queryCache } from 'react-query'
-
 queryCache.setQueryData('todos', todos)
 ```
diff --git a/docs/src/pages/docs/guides/query-invalidation.md b/docs/src/pages/docs/guides/query-invalidation.md
index 67cf5abeb..d4a302f4d 100644
--- a/docs/src/pages/docs/guides/query-invalidation.md
+++ b/docs/src/pages/docs/guides/query-invalidation.md
@@ -6,8 +6,6 @@ title: Query Invalidation
 Waiting for queries to become stale before they are fetched again doesn't always work, especially when you know for a fact that a query needs to get refetched. For that purpose, the `queryCache` has an `invalidateQueries` method that lets you manually mark queries as stale and potentially refetch them too!
 
 ```js
-import { queryCache } from 'react-query'
-
 queryCache.invalidateQueries('todos')
 ```
 
@@ -25,7 +23,10 @@ When using APIs like `invalidateQueries` and `removeQueries` (and others that su
 In this example, we can use the `todos` prefix to invalidate any queries that start with `todos` in their query key:
 
 ```js
-import { queryCache, useQuery } from 'react-query'
+import { useQuery, useQueryCache } from 'react-query'
+
+// Get QueryCache from the context
+const queryCache = useQueryCache()
 
 queryCache.invalidateQueries('todos')
 
diff --git a/docs/src/pages/docs/guides/ssr.md b/docs/src/pages/docs/guides/ssr.md
index 338ee2eb6..797bebf8a 100644
--- a/docs/src/pages/docs/guides/ssr.md
+++ b/docs/src/pages/docs/guides/ssr.md
@@ -59,12 +59,14 @@ To support caching queries on the server and set up hydration, you start with wr
 
 ```jsx
 // _app.jsx
-import { ReactQueryCacheProvider } from 'react-query'
+import { ReactQueryCacheProvider, QueryCache } from 'react-query'
 import { Hydrate } from 'react-query/hydration'
 
+const queryCache = new QueryCache()
+
 export default function MyApp({ Component, pageProps }) {
   return (
-    <ReactQueryCacheProvider>
+    <ReactQueryCacheProvider queryCache={queryCache}>
       <Hydrate state={pageProps.dehydratedState}>
         <Component {...pageProps} />
       </Hydrate>
@@ -77,11 +79,11 @@ Now you are ready to prefetch some data in your pages with either [`getStaticPro
 
 ```jsx
 // pages/posts.jsx
-import { makeQueryCache } from 'react-query'
+import { QueryCache } from 'react-query'
 import { dehydrate } from 'react-query/hydration'
 
 export async function getStaticProps() {
-  const queryCache = makeQueryCache()
+  const queryCache = new QueryCache()
 
   await queryCache.prefetchQuery('posts', getPosts)
 
@@ -116,37 +118,38 @@ Since there are many different possible setups for SSR, it's hard to give a deta
 > Note: The global `queryCache` you can import directly from 'react-query' does not cache queries on the server to avoid leaking sensitive information between requests.
 
 - Prefetch data
-  - Create a `prefetchQueryCache` specifically for prefetching by calling `const prefetchQueryCache = makeQueryCache()`
+  - Create a `prefetchQueryCache` specifically for prefetching by calling `const prefetchQueryCache = new QueryCache()`
   - Call `prefetchQueryCache.prefetchQuery(...)` to prefetch queries
   - Dehydrate by using `const dehydratedState = dehydrate(prefetchQueryCache)`
 - Render
-  - Wrap the app in `<ReactQueryCacheProvider>` and `<Hydrate>` to create a new cache and hydrate the state
-    - This makes sure a separate `queryCache` is created specifically for rendering
-    - **Do not** pass in the `prefetchQueryCache` from the last step, the server and client both needs to render from the dehydrated data to avoid React hydration mismatches. This is because queries with errors are excluded from dehydration by default.
+  - Create a new render query cache and hydrate the state. Use this query cache to render your app.
+    - **Do not** use the `prefetchQueryCache` to render your app, the server and client both needs to render from the dehydrated data to avoid React hydration mismatches. This is because queries with errors are excluded from dehydration by default.
 - Serialize and embed `dehydratedState` in the markup
   - Security note: Serializing data with `JSON.stringify` can put you at risk for XSS-vulnerabilities, [this blog post explains why and how to solve it](https://medium.com/node-security/the-most-common-xss-vulnerability-in-react-js-applications-2bdffbcc1fa0)
 
 **Client side**
 
 - Parse `dehydratedState` from where you put it in the markup
+- Create a cache and hydrate the state
 - Render
-  - Wrap the app in `<Hydrate>` from `'react-query/hydration'` and pass in the dehyrated state.
 
 This list aims to be exhaustive, but depending on your current setup, the above steps can take more or less work. Here is a barebones example:
 
 ```jsx
 // Server
-const prefetchCache = makeQueryCache()
+const prefetchCache = new QueryCache()
 await prefetchCache.prefetchQuery('key', fn)
 const dehydratedState = dehydrate(prefetchCache)
 
+const renderCache = new QueryCache()
+hydrate(renderCache, dehydratedState)
+
 const html = ReactDOM.renderToString(
-  <ReactQueryCacheProvider>
-    <Hydrate state={dehyratedState}>
-      <App />
-    </Hydrate>
+  <ReactQueryCacheProvider queryCache={renderCache}>
+    <App />
   </ReactQueryCacheProvider>
 )
+
 res.send(`
 <html>
   <body>
@@ -160,11 +163,13 @@ res.send(`
 
 // Client
 const dehydratedState = JSON.parse(window.__REACT_QUERY_INITIAL_QUERIES__)
+
+const queryCache = new QueryCache()
+hydrate(queryCache, dehydratedState)
+
 ReactDOM.hydrate(
-  <ReactQueryCacheProvider>
-    <Hydrate state={dehyratedState}>
-      <App />
-    </Hydrate>
+  <ReactQueryCacheProvider queryCache={queryCache}>
+    <App />
   </ReactQueryCacheProvider>
 )
 ```
diff --git a/docs/src/pages/docs/guides/suspense.md b/docs/src/pages/docs/guides/suspense.md
index 11f0d2442..f08209449 100644
--- a/docs/src/pages/docs/guides/suspense.md
+++ b/docs/src/pages/docs/guides/suspense.md
@@ -14,7 +14,7 @@ Global configuration:
 import { ReactQueryConfigProvider } from 'react-query'
 
 const queryConfig = {
-  shared: {
+  queries: {
     suspense: true,
   },
 }
diff --git a/docs/src/pages/docs/overview.md b/docs/src/pages/docs/overview.md
index 7318aa849..54b261c43 100644
--- a/docs/src/pages/docs/overview.md
+++ b/docs/src/pages/docs/overview.md
@@ -47,9 +47,19 @@ In the example below, you can see React Query in its most basic and simple form
 [Open in CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-query/tree/master/examples/simple)
 
 ```js
-import { useQuery } from 'react-query'
+import { useQuery, QueryCache, ReactQueryCacheProvider } from 'react-query'
+
+const queryCache = new QueryCache()
 
 export default function App() {
+  return (
+    <ReactQueryCacheProvider queryCache={queryCache}>
+      <Example />
+    </ReactQueryCacheProvider>
+  )
+}
+
+function Example() {
   const { isLoading, error, data } = useQuery('repoData', () =>
     fetch('https://api.github.com/repos/tannerlinsley/react-query').then(res =>
       res.json()
diff --git a/docs/src/pages/docs/quick-start.md b/docs/src/pages/docs/quick-start.md
index db56d8f86..7a29d3015 100644
--- a/docs/src/pages/docs/quick-start.md
+++ b/docs/src/pages/docs/quick-start.md
@@ -10,10 +10,23 @@ This example very briefly illustrates the 3 core concepts of React Query:
 - Query Invalidation
 
 ```js
-import { useQuery, useMutation, queryCache } from 'react-query'
+import { useQuery, useMutation, useQueryCache, QueryCache, ReactQueryCacheProvider } from 'react-query'
 import { getTodos, postTodo } from '../my-api'
 
+const queryCache = new QueryCache()
+
+function App() {
+  return (
+    <ReactQueryCacheProvider queryCache={queryCache}>
+      <Todos />
+    </ReactQueryCacheProvider>
+  );
+}
+
 function Todos() {
+  // Cache
+  const cache = useQueryCache()
+
   // Queries
   const todosQuery = useQuery('todos', getTodos)
 
@@ -21,7 +34,7 @@ function Todos() {
   const [addTodo] = useMutation(postTodo, {
     onSuccess: () => {
       // Query Invalidations
-      queryCache.invalidateQueries('todos')
+      cache.invalidateQueries('todos')
     },
   })
 
@@ -46,6 +59,8 @@ function Todos() {
     </div>
   )
 }
+
+render(<App />, document.getElementById('root'));
 ```
 
 These three concepts make up most of the core functionality of React Query. The next sections of the documentation will go over each of these core concepts in great detail.
diff --git a/examples/auto-refetching/pages/index.js b/examples/auto-refetching/pages/index.js
index 96b2d67fb..4830ed479 100755
--- a/examples/auto-refetching/pages/index.js
+++ b/examples/auto-refetching/pages/index.js
@@ -3,10 +3,27 @@ import axios from 'axios'
 
 //
 
-import { useQuery, useMutation, queryCache } from 'react-query'
+import {
+  useQuery,
+  useQueryCache,
+  useMutation,
+  QueryCache,
+  ReactQueryCacheProvider,
+} from 'react-query'
 import { ReactQueryDevtools } from 'react-query-devtools'
 
-function App() {
+const queryCache = new QueryCache()
+
+export default function App() {
+  return (
+    <ReactQueryCacheProvider queryCache={queryCache}>
+      <Example />
+    </ReactQueryCacheProvider>
+  )
+}
+
+function Example() {
+  const cache = useQueryCache()
   const [intervalMs, setIntervalMs] = React.useState(1000)
   const [value, setValue] = React.useState('')
 
@@ -25,12 +42,12 @@ function App() {
   const [mutateAddTodo] = useMutation(
     value => fetch(`/api/data?add=${value}`),
     {
-      onSuccess: () => queryCache.invalidateQueries('todos'),
+      onSuccess: () => cache.invalidateQueries('todos'),
     }
   )
 
   const [mutateClear] = useMutation(value => fetch(`/api/data?clear=1`), {
-    onSuccess: () => queryCache.invalidateQueries('todos'),
+    onSuccess: () => cache.invalidateQueries('todos'),
   })
 
   if (status === 'loading') return <h1>Loading...</h1>
@@ -93,10 +110,3 @@ function App() {
     </div>
   )
 }
-
-export default () => (
-  <>
-    <App />
-    <App />
-  </>
-)
diff --git a/examples/basic/src/index.js b/examples/basic/src/index.js
index 888eb1417..0b2a8a7e9 100644
--- a/examples/basic/src/index.js
+++ b/examples/basic/src/index.js
@@ -2,14 +2,21 @@
 import React from "react";
 import ReactDOM from "react-dom";
 import axios from "axios";
-import { useQuery, queryCache } from "react-query";
+import {
+  useQuery,
+  useQueryCache,
+  QueryCache,
+  ReactQueryCacheProvider,
+} from "react-query";
 import { ReactQueryDevtools } from "react-query-devtools";
 
+const queryCache = new QueryCache();
+
 function App() {
   const [postId, setPostId] = React.useState(-1);
 
   return (
-    <>
+    <ReactQueryCacheProvider queryCache={queryCache}>
       <p>
         As you visit the posts below, you will notice them in a loading state
         the first time you load them. However, after you return to this list and
@@ -26,7 +33,7 @@ function App() {
         <Posts setPostId={setPostId} />
       )}
       <ReactQueryDevtools initialIsOpen />
-    </>
+    </ReactQueryCacheProvider>
   );
 }
 
@@ -40,6 +47,7 @@ function usePosts() {
 }
 
 function Posts({ setPostId }) {
+  const cache = useQueryCache();
   const { status, data, error, isFetching } = usePosts();
 
   return (
@@ -61,7 +69,7 @@ function Posts({ setPostId }) {
                     style={
                       // We can use the queryCache here to show bold links for
                       // ones that are cached
-                      queryCache.getQueryData(["post", post.id])
+                      cache.getQueryData(["post", post.id])
                         ? {
                             fontWeight: "bold",
                             color: "green",
diff --git a/examples/custom-hooks/src/index.js b/examples/custom-hooks/src/index.js
index 0dc5aebae..fd6c093b8 100644
--- a/examples/custom-hooks/src/index.js
+++ b/examples/custom-hooks/src/index.js
@@ -1,17 +1,23 @@
 /* eslint-disable jsx-a11y/anchor-is-valid */
 import React from "react";
 import ReactDOM from "react-dom";
-import { queryCache } from "react-query";
+import {
+  useQueryCache,
+  QueryCache,
+  ReactQueryCacheProvider,
+} from "react-query";
 import { ReactQueryDevtools } from "react-query-devtools";
 
 import usePosts from "./hooks/usePosts";
 import usePost from "./hooks/usePost";
 
+const queryCache = new QueryCache();
+
 function App() {
   const [postId, setPostId] = React.useState(-1);
 
   return (
-    <>
+    <ReactQueryCacheProvider queryCache={queryCache}>
       <p>
         This example is exactly the same as the basic example, but each query
         has been refactored to be it's own custom hook. This design is the
@@ -24,11 +30,12 @@ function App() {
         <Posts setPostId={setPostId} />
       )}
       <ReactQueryDevtools initialIsOpen />
-    </>
+    </ReactQueryCacheProvider>
   );
 }
 
 function Posts({ setPostId }) {
+  const cache = useQueryCache();
   const { status, data, error, isFetching } = usePosts();
 
   return (
@@ -50,7 +57,7 @@ function Posts({ setPostId }) {
                     style={
                       // We can use the queryCache here to show bold links for
                       // ones that are cached
-                      queryCache.getQueryData(["post", post.id])
+                      cache.getQueryData(["post", post.id])
                         ? {
                             fontWeight: "bold",
                             color: "green",
diff --git a/examples/default-query-function/src/index.js b/examples/default-query-function/src/index.js
index dcb363fc9..9ae3eb31a 100644
--- a/examples/default-query-function/src/index.js
+++ b/examples/default-query-function/src/index.js
@@ -2,7 +2,12 @@
 import React from "react";
 import ReactDOM from "react-dom";
 import axios from "axios";
-import { useQuery, queryCache, ReactQueryConfigProvider } from "react-query";
+import {
+  useQuery,
+  useQueryCache,
+  QueryCache,
+  ReactQueryCacheProvider,
+} from "react-query";
 import { ReactQueryDevtools } from "react-query-devtools";
 
 // Define a default query function that will receive the query key
@@ -13,41 +18,43 @@ const defaultQueryFn = async (key) => {
   return data;
 };
 
+// provide the default query function to your app via the config provider
+const queryCache = new QueryCache({
+  defaultConfig: {
+    queries: {
+      queryFn: defaultQueryFn,
+    },
+  },
+});
+
 function App() {
   const [postId, setPostId] = React.useState(-1);
 
   return (
-    <>
-      <ReactQueryConfigProvider
-        // provide the default query function to your app via the config provider
-        config={{
-          queries: {
-            queryFn: defaultQueryFn,
-          },
-        }}
-      >
-        <p>
-          As you visit the posts below, you will notice them in a loading state
-          the first time you load them. However, after you return to this list
-          and click on any posts you have already visited again, you will see
-          them load instantly and background refresh right before your eyes!{" "}
-          <strong>
-            (You may need to throttle your network speed to simulate longer
-            loading sequences)
-          </strong>
-        </p>
-        {postId > -1 ? (
-          <Post postId={postId} setPostId={setPostId} />
-        ) : (
-          <Posts setPostId={setPostId} />
-        )}
-      </ReactQueryConfigProvider>
+    <ReactQueryCacheProvider queryCache={queryCache}>
+      <p>
+        As you visit the posts below, you will notice them in a loading state
+        the first time you load them. However, after you return to this list and
+        click on any posts you have already visited again, you will see them
+        load instantly and background refresh right before your eyes!{" "}
+        <strong>
+          (You may need to throttle your network speed to simulate longer
+          loading sequences)
+        </strong>
+      </p>
+      {postId > -1 ? (
+        <Post postId={postId} setPostId={setPostId} />
+      ) : (
+        <Posts setPostId={setPostId} />
+      )}
       <ReactQueryDevtools initialIsOpen />
-    </>
+    </ReactQueryCacheProvider>
   );
 }
 
 function Posts({ setPostId }) {
+  const cache = useQueryCache();
+
   // All you have to do now is pass a key!
   const { status, data, error, isFetching } = useQuery("/posts");
 
@@ -70,7 +77,7 @@ function Posts({ setPostId }) {
                     style={
                       // We can use the queryCache here to show bold links for
                       // ones that are cached
-                      queryCache.getQueryData(["post", post.id])
+                      cache.getQueryData(["post", post.id])
                         ? {
                             fontWeight: "bold",
                             color: "green",
diff --git a/examples/focus-refetching/pages/index.js b/examples/focus-refetching/pages/index.js
index 259d4db07..96cd58048 100755
--- a/examples/focus-refetching/pages/index.js
+++ b/examples/focus-refetching/pages/index.js
@@ -1,21 +1,39 @@
 import React from 'react'
 import axios from 'axios'
 
-import { useQuery, useMutation, queryCache } from 'react-query'
+import {
+  useQuery,
+  useQueryCache,
+  useMutation,
+  QueryCache,
+  ReactQueryCacheProvider,
+} from 'react-query'
 import { ReactQueryDevtools } from 'react-query-devtools'
 
-export default () => {
+const queryCache = new QueryCache()
+
+export default function App() {
+  return (
+    <ReactQueryCacheProvider queryCache={queryCache}>
+      <Example />
+    </ReactQueryCacheProvider>
+  )
+}
+
+function Example() {
+  const cache = useQueryCache()
+
   const { status, data, error } = useQuery('user', async () => {
     const { data } = await axios.get('/api/user')
     return data
   })
 
   const [logoutMutation] = useMutation(logout, {
-    onSuccess: () => queryCache.invalidateQueries('user'),
+    onSuccess: () => cache.invalidateQueries('user'),
   })
 
   const [loginMutation] = useMutation(login, {
-    onSuccess: () => queryCache.invalidateQueries('user'),
+    onSuccess: () => cache.invalidateQueries('user'),
   })
 
   return (
diff --git a/examples/load-more-infinite-scroll/pages/index.js b/examples/load-more-infinite-scroll/pages/index.js
index 1d066df32..faa2240c7 100755
--- a/examples/load-more-infinite-scroll/pages/index.js
+++ b/examples/load-more-infinite-scroll/pages/index.js
@@ -2,14 +2,28 @@ import React from 'react'
 import Link from 'next/link'
 import axios from 'axios'
 
-import { useInfiniteQuery } from 'react-query'
+import {
+  useInfiniteQuery,
+  QueryCache,
+  ReactQueryCacheProvider,
+} from 'react-query'
 import { ReactQueryDevtools } from 'react-query-devtools'
 
 //
 
 import useIntersectionObserver from '../hooks/useIntersectionObserver'
 
-export default () => {
+const queryCache = new QueryCache()
+
+export default function App() {
+  return (
+    <ReactQueryCacheProvider queryCache={queryCache}>
+      <Example />
+    </ReactQueryCacheProvider>
+  )
+}
+
+function Example() {
   const {
     status,
     data,
diff --git a/examples/optimistic-updates/pages/index.js b/examples/optimistic-updates/pages/index.js
index a526e446a..7fe69f615 100755
--- a/examples/optimistic-updates/pages/index.js
+++ b/examples/optimistic-updates/pages/index.js
@@ -1,10 +1,27 @@
 import React from 'react'
 import axios from 'axios'
 
-import { useQuery, useMutation, queryCache } from 'react-query'
+import {
+  useQuery,
+  useQueryCache,
+  useMutation,
+  QueryCache,
+  ReactQueryCacheProvider,
+} from 'react-query'
 import { ReactQueryDevtools } from 'react-query-devtools'
 
-export default () => {
+const queryCache = new QueryCache()
+
+export default function App() {
+  return (
+    <ReactQueryCacheProvider queryCache={queryCache}>
+      <Example />
+    </ReactQueryCacheProvider>
+  )
+}
+
+function Example() {
+  const cache = useQueryCache()
   const [text, setText] = React.useState('')
   const { status, data, error, isFetching } = useQuery('todos', async () => {
     const { data } = await axios.get('/api/data')
@@ -19,11 +36,11 @@ export default () => {
       // an error
       onMutate: text => {
         setText('')
-        queryCache.cancelQueries('todos')
+        cache.cancelQueries('todos')
 
-        const previousValue = queryCache.getQueryData('todos')
+        const previousValue = cache.getQueryData('todos')
 
-        queryCache.setQueryData('todos', old => ({
+        cache.setQueryData('todos', old => ({
           ...old,
           items: [...old.items, text],
         }))
@@ -32,10 +49,10 @@ export default () => {
       },
       // On failure, roll back to the previous value
       onError: (err, variables, previousValue) =>
-        queryCache.setQueryData('todos', previousValue),
+        cache.setQueryData('todos', previousValue),
       // After success or failure, refetch the todos query
       onSettled: () => {
-        queryCache.invalidateQueries('todos')
+        cache.invalidateQueries('todos')
       },
     }
   )
diff --git a/examples/pagination/pages/index.js b/examples/pagination/pages/index.js
index fc7497aa0..361600007 100644
--- a/examples/pagination/pages/index.js
+++ b/examples/pagination/pages/index.js
@@ -1,9 +1,25 @@
 import React from 'react'
 import axios from 'axios'
-import { usePaginatedQuery, queryCache } from 'react-query'
+import {
+  usePaginatedQuery,
+  useQueryCache,
+  QueryCache,
+  ReactQueryCacheProvider,
+} from 'react-query'
 import { ReactQueryDevtools } from 'react-query-devtools'
 
-function Todos() {
+const queryCache = new QueryCache()
+
+export default function App() {
+  return (
+    <ReactQueryCacheProvider queryCache={queryCache}>
+      <Example />
+    </ReactQueryCacheProvider>
+  )
+}
+
+function Example() {
+  const cache = useQueryCache()
   const [page, setPage] = React.useState(0)
 
   const fetchProjects = React.useCallback(async (key, page = 0) => {
@@ -22,7 +38,7 @@ function Todos() {
   // Prefetch the next page!
   React.useEffect(() => {
     if (latestData?.hasMore) {
-      queryCache.prefetchQuery(['projects', page + 1], fetchProjects)
+      cache.prefetchQuery(['projects', page + 1], fetchProjects)
     }
   }, [latestData, fetchProjects, page])
 
@@ -76,5 +92,3 @@ function Todos() {
     </div>
   )
 }
-
-export default Todos
diff --git a/examples/playground/src/index.js b/examples/playground/src/index.js
index e3c482795..dee927c32 100644
--- a/examples/playground/src/index.js
+++ b/examples/playground/src/index.js
@@ -2,10 +2,12 @@ import React from "react";
 import ReactDOM from "react-dom";
 
 import {
+  ReactQueryCacheProvider,
   ReactQueryConfigProvider,
   useQuery,
+  useQueryCache,
   useMutation,
-  queryCache,
+  QueryCache,
 } from "react-query";
 
 import { ReactQueryDevtools } from "react-query-devtools";
@@ -26,6 +28,8 @@ let errorRate = 0.05;
 let queryTimeMin = 1000;
 let queryTimeMax = 2000;
 
+const queryCache = new QueryCache();
+
 function Root() {
   const [staleTime, setStaleTime] = React.useState(1000);
   const [cacheTime, setCacheTime] = React.useState(3000);
@@ -54,78 +58,85 @@ function Root() {
   );
 
   return (
-    <ReactQueryConfigProvider config={queryConfig}>
-      <p>
-        The "staleTime" and "cacheTime" durations have been altered in this
-        example to show how query stale-ness and query caching work on a
-        granular level
-      </p>
-      <div>
-        Stale Time:{" "}
-        <input
-          type="number"
-          min="0"
-          step="1000"
-          value={staleTime}
-          onChange={(e) => setStaleTime(parseFloat(e.target.value, 10))}
-          style={{ width: "100px" }}
-        />
-      </div>
-      <div>
-        Cache Time:{" "}
-        <input
-          type="number"
-          min="0"
-          step="1000"
-          value={cacheTime}
-          onChange={(e) => setCacheTime(parseFloat(e.target.value, 10))}
-          style={{ width: "100px" }}
-        />
-      </div>
-      <br />
-      <div>
-        Error Rate:{" "}
-        <input
-          type="number"
-          min="0"
-          max="1"
-          step=".05"
-          value={localErrorRate}
-          onChange={(e) => setErrorRate(parseFloat(e.target.value, 10))}
-          style={{ width: "100px" }}
-        />
-      </div>
-      <div>
-        Fetch Time Min:{" "}
-        <input
-          type="number"
-          min="1"
-          step="500"
-          value={localFetchTimeMin}
-          onChange={(e) => setLocalFetchTimeMin(parseFloat(e.target.value, 10))}
-          style={{ width: "60px" }}
-        />{" "}
-      </div>
-      <div>
-        Fetch Time Max:{" "}
-        <input
-          type="number"
-          min="1"
-          step="500"
-          value={localFetchTimeMax}
-          onChange={(e) => setLocalFetchTimeMax(parseFloat(e.target.value, 10))}
-          style={{ width: "60px" }}
-        />
-      </div>
-      <br />
-      <App />
-      <br />
-      <ReactQueryDevtools initialIsOpen />
-    </ReactQueryConfigProvider>
+    <ReactQueryCacheProvider queryCache={queryCache}>
+      <ReactQueryConfigProvider config={queryConfig}>
+        <p>
+          The "staleTime" and "cacheTime" durations have been altered in this
+          example to show how query stale-ness and query caching work on a
+          granular level
+        </p>
+        <div>
+          Stale Time:{" "}
+          <input
+            type="number"
+            min="0"
+            step="1000"
+            value={staleTime}
+            onChange={(e) => setStaleTime(parseFloat(e.target.value, 10))}
+            style={{ width: "100px" }}
+          />
+        </div>
+        <div>
+          Cache Time:{" "}
+          <input
+            type="number"
+            min="0"
+            step="1000"
+            value={cacheTime}
+            onChange={(e) => setCacheTime(parseFloat(e.target.value, 10))}
+            style={{ width: "100px" }}
+          />
+        </div>
+        <br />
+        <div>
+          Error Rate:{" "}
+          <input
+            type="number"
+            min="0"
+            max="1"
+            step=".05"
+            value={localErrorRate}
+            onChange={(e) => setErrorRate(parseFloat(e.target.value, 10))}
+            style={{ width: "100px" }}
+          />
+        </div>
+        <div>
+          Fetch Time Min:{" "}
+          <input
+            type="number"
+            min="1"
+            step="500"
+            value={localFetchTimeMin}
+            onChange={(e) =>
+              setLocalFetchTimeMin(parseFloat(e.target.value, 10))
+            }
+            style={{ width: "60px" }}
+          />{" "}
+        </div>
+        <div>
+          Fetch Time Max:{" "}
+          <input
+            type="number"
+            min="1"
+            step="500"
+            value={localFetchTimeMax}
+            onChange={(e) =>
+              setLocalFetchTimeMax(parseFloat(e.target.value, 10))
+            }
+            style={{ width: "60px" }}
+          />
+        </div>
+        <br />
+        <App />
+        <br />
+        <ReactQueryDevtools initialIsOpen />
+      </ReactQueryConfigProvider>
+    </ReactQueryCacheProvider>
   );
 }
 
 function App() {
+  const cache = useQueryCache();
   const [editingIndex, setEditingIndex] = React.useState(null);
   const [views, setViews] = React.useState(["", "fruit", "grape"]);
   // const [views, setViews] = React.useState([""]);
@@ -133,7 +144,7 @@ function App() {
   return (
     <div className="App">
       <div>
-        <button onClick={() => queryCache.invalidateQueries(true)}>
+        <button onClick={() => cache.invalidateQueries(true)}>
           Force Refetch All
         </button>
       </div>
@@ -227,6 +238,8 @@ function Todos({ initialFilter = "", setEditingIndex }) {
 }
 
 function EditTodo({ editingIndex, setEditingIndex }) {
+  const cache = useQueryCache();
+
   // Don't attempt to query until editingIndex is truthy
   const { status, data, isFetching, error, failureCount, refetch } = useQuery(
     ["todo", { id: editingIndex }],
@@ -249,8 +262,8 @@ function EditTodo({ editingIndex, setEditingIndex }) {
   const [mutate, mutationState] = useMutation(patchTodo, {
     onSuccess: (data) => {
       // Update `todos` and the individual todo queries when this mutation succeeds
-      queryCache.invalidateQueries("todos");
-      queryCache.setQueryData(["todo", { id: editingIndex }], data);
+      cache.invalidateQueries("todos");
+      cache.setQueryData(["todo", { id: editingIndex }], data);
     },
   });
 
@@ -328,11 +341,12 @@ function EditTodo({ editingIndex, setEditingIndex }) {
 }
 
 function AddTodo() {
+  const cache = useQueryCache();
   const [name, setName] = React.useState("");
 
   const [mutate, { status, error }] = useMutation(postTodo, {
     onSuccess: () => {
-      queryCache.invalidateQueries("todos");
+      cache.invalidateQueries("todos");
     },
   });
 
diff --git a/examples/prefetching/pages/index.js b/examples/prefetching/pages/index.js
index efad216f9..9b937e7e6 100755
--- a/examples/prefetching/pages/index.js
+++ b/examples/prefetching/pages/index.js
@@ -1,6 +1,11 @@
 import React from 'react'
 import axios from 'axios'
-import { useQuery, queryCache } from 'react-query'
+import {
+  useQuery,
+  useQueryCache,
+  QueryCache,
+  ReactQueryCacheProvider,
+} from 'react-query'
 import { ReactQueryDevtools } from 'react-query-devtools'
 
 const getCharacters = async () => {
@@ -17,7 +22,18 @@ const getCharacter = async (key, selectedChar) => {
   return data
 }
 
+const queryCache = new QueryCache()
+
 export default function App() {
+  return (
+    <ReactQueryCacheProvider queryCache={queryCache}>
+      <Example />
+    </ReactQueryCacheProvider>
+  )
+}
+
+function Example() {
+  const cache = useQueryCache()
   const rerender = React.useReducer(d => d + 1)[1]
   const [selectedChar, setSelectedChar] = React.useState(1)
 
@@ -30,10 +46,10 @@ export default function App() {
 
   const prefetchNext = async id => {
     await Promise.all([
-      queryCache.prefetchQuery(['character', id + 1], getCharacter, {
+      cache.prefetchQuery(['character', id + 1], getCharacter, {
         staleTime: 5 * 60 * 1000,
       }),
-      queryCache.prefetchQuery(['character', id - 1], getCharacter, {
+      cache.prefetchQuery(['character', id - 1], getCharacter, {
         staleTime: 5 * 60 * 1000,
       }),
     ])
@@ -61,7 +77,7 @@ export default function App() {
           >
             <div
               style={
-                queryCache.getQueryData(['character', char.id])
+                cache.getQueryData(['character', char.id])
                   ? {
                       fontWeight: 'bold',
                     }
diff --git a/examples/rick-morty/src/App.js b/examples/rick-morty/src/App.js
index 1643692e9..578bdc594 100644
--- a/examples/rick-morty/src/App.js
+++ b/examples/rick-morty/src/App.js
@@ -3,19 +3,24 @@ import { BrowserRouter as Router } from "react-router-dom";
 import { ThemeProvider } from "@material-ui/core";
 import { createMuiTheme } from "@material-ui/core/styles";
 
+import { QueryCache, ReactQueryCacheProvider } from "react-query";
 import { ReactQueryDevtools } from "react-query-devtools";
 
 import "./styles.css";
 import Layout from "./Layout";
 
+const queryCache = new QueryCache();
+
 export default function App() {
   return (
-    <Router>
-      <ThemeProvider theme={theme}>
-        <Layout />
-        <ReactQueryDevtools initialIsOpen />
-      </ThemeProvider>
-    </Router>
+    <ReactQueryCacheProvider queryCache={queryCache}>
+      <Router>
+        <ThemeProvider theme={theme}>
+          <Layout />
+          <ReactQueryDevtools initialIsOpen />
+        </ThemeProvider>
+      </Router>
+    </ReactQueryCacheProvider>
   );
 }
 
diff --git a/examples/simple/src/index.js b/examples/simple/src/index.js
index 145de4b2a..72a033615 100644
--- a/examples/simple/src/index.js
+++ b/examples/simple/src/index.js
@@ -1,10 +1,20 @@
 /* eslint-disable jsx-a11y/anchor-is-valid */
 import React from "react";
 import ReactDOM from "react-dom";
-import { useQuery } from "react-query";
+import { useQuery, QueryCache, ReactQueryCacheProvider } from "react-query";
 import { ReactQueryDevtools } from "react-query-devtools";
 
+const queryCache = new QueryCache();
+
 export default function App() {
+  return (
+    <ReactQueryCacheProvider queryCache={queryCache}>
+      <Example />
+    </ReactQueryCacheProvider>
+  );
+}
+
+function Example() {
   const { isLoading, error, data } = useQuery("repoData", () =>
     fetch(
       "https://api.github.com/repos/tannerlinsley/react-query"
diff --git a/examples/star-wars/src/App.js b/examples/star-wars/src/App.js
index 1643692e9..578bdc594 100644
--- a/examples/star-wars/src/App.js
+++ b/examples/star-wars/src/App.js
@@ -3,19 +3,24 @@ import { BrowserRouter as Router } from "react-router-dom";
 import { ThemeProvider } from "@material-ui/core";
 import { createMuiTheme } from "@material-ui/core/styles";
 
+import { QueryCache, ReactQueryCacheProvider } from "react-query";
 import { ReactQueryDevtools } from "react-query-devtools";
 
 import "./styles.css";
 import Layout from "./Layout";
 
+const queryCache = new QueryCache();
+
 export default function App() {
   return (
-    <Router>
-      <ThemeProvider theme={theme}>
-        <Layout />
-        <ReactQueryDevtools initialIsOpen />
-      </ThemeProvider>
-    </Router>
+    <ReactQueryCacheProvider queryCache={queryCache}>
+      <Router>
+        <ThemeProvider theme={theme}>
+          <Layout />
+          <ReactQueryDevtools initialIsOpen />
+        </ThemeProvider>
+      </Router>
+    </ReactQueryCacheProvider>
   );
 }
 
diff --git a/examples/suspense/src/components/Projects.js b/examples/suspense/src/components/Projects.js
index f67d2fc3f..ea1510487 100644
--- a/examples/suspense/src/components/Projects.js
+++ b/examples/suspense/src/components/Projects.js
@@ -1,5 +1,5 @@
 import React from "react";
-import { useQuery, queryCache } from "react-query";
+import { useQuery, useQueryCache } from "react-query";
 
 import Button from "./Button";
 import Spinner from "./Spinner";
@@ -7,17 +7,18 @@ import Spinner from "./Spinner";
 import { fetchProjects, fetchProject } from "../queries";
 
 export default function Projects({ setActiveProject }) {
+  const cache = useQueryCache();
   const { data, isFetching } = useQuery("projects", fetchProjects);
 
   return (
     <div>
       <h1>Projects {isFetching ? <Spinner /> : null}</h1>
-      {data.map(project => (
+      {data.map((project) => (
         <p key={project.name}>
           <Button
             onClick={() => {
               // Prefetch the project query
-              queryCache.prefetchQuery(
+              cache.prefetchQuery(
                 ["project", { id: project.name }],
                 fetchProject
               );
diff --git a/examples/suspense/src/index.js b/examples/suspense/src/index.js
index 31ed55a48..72be79ddb 100755
--- a/examples/suspense/src/index.js
+++ b/examples/suspense/src/index.js
@@ -1,6 +1,10 @@
 import React, { lazy } from "react";
 import ReactDOM from "react-dom";
-import { ReactQueryConfigProvider, queryCache } from "react-query";
+import {
+  useQueryCache,
+  QueryCache,
+  ReactQueryCacheProvider,
+} from "react-query";
 import { ReactQueryDevtools } from "react-query-devtools";
 import { ErrorBoundary } from "react-error-boundary";
 
@@ -13,26 +17,35 @@ import Button from "./components/Button";
 const Projects = lazy(() => import("./components/Projects"));
 const Project = lazy(() => import("./components/Project"));
 
-const queryConfig = {
-  shared: {
-    suspense: true,
+const queryCache = new QueryCache({
+  defaultConfig: {
+    queries: {
+      retry: 0,
+      suspense: true,
+    },
   },
-  queries: {
-    retry: 0,
-  },
-};
+});
 
 function App() {
+  return (
+    <ReactQueryCacheProvider queryCache={queryCache}>
+      <Example />
+    </ReactQueryCacheProvider>
+  );
+}
+
+function Example() {
+  const cache = useQueryCache();
   const [showProjects, setShowProjects] = React.useState(false);
   const [activeProject, setActiveProject] = React.useState(null);
 
   return (
-    <ReactQueryConfigProvider config={queryConfig}>
+    <>
       <Button
         onClick={() => {
           setShowProjects((old) => {
             if (!old) {
-              queryCache.prefetchQuery("projects", fetchProjects);
+              cache.prefetchQuery("projects", fetchProjects);
             }
             return !old;
           });
@@ -51,7 +64,7 @@ function App() {
             <pre style={{ whiteSpace: "normal" }}>{error.message}</pre>
           </div>
         )}
-        onReset={() => queryCache.resetErrorBoundaries()}
+        onReset={() => cache.resetErrorBoundaries()}
       >
         <React.Suspense fallback={<h1>Loading projects...</h1>}>
           {showProjects ? (
@@ -67,7 +80,7 @@ function App() {
         </React.Suspense>
       </ErrorBoundary>
       <ReactQueryDevtools initialIsOpen />
-    </ReactQueryConfigProvider>
+    </>
   );
 }
 
diff --git a/src/core/config.ts b/src/core/config.ts
index 215b1abe1..9780685bd 100644
--- a/src/core/config.ts
+++ b/src/core/config.ts
@@ -68,7 +68,6 @@ export const DEFAULT_CONFIG: ReactQueryConfig = {
 
 export function getDefaultReactQueryConfig() {
   return {
-    shared: { ...DEFAULT_CONFIG.shared },
     queries: { ...DEFAULT_CONFIG.queries },
     mutations: { ...DEFAULT_CONFIG.mutations },
   }
@@ -103,7 +102,6 @@ export function getResolvedQueryConfig<TResult, TError>(
   const queryCacheConfig = queryCache.getDefaultConfig()
 
   const resolvedConfig = {
-    ...DEFAULT_CONFIG.shared,
     ...DEFAULT_CONFIG.queries,
     ...queryCacheConfig?.shared,
     ...queryCacheConfig?.queries,
@@ -133,7 +131,6 @@ export function getResolvedMutationConfig<
 ): MutationConfig<TResult, TError, TVariables, TSnapshot> {
   const queryCacheConfig = queryCache.getDefaultConfig()
   return {
-    ...DEFAULT_CONFIG.shared,
     ...DEFAULT_CONFIG.mutations,
     ...queryCacheConfig?.shared,
     ...queryCacheConfig?.mutations,
diff --git a/src/core/index.ts b/src/core/index.ts
index 5c5b24081..4a6d20e43 100644
--- a/src/core/index.ts
+++ b/src/core/index.ts
@@ -1,5 +1,10 @@
 export { getDefaultReactQueryConfig } from './config'
-export { queryCache, queryCaches, makeQueryCache } from './queryCache'
+export {
+  queryCache,
+  queryCaches,
+  makeQueryCache,
+  QueryCache,
+} from './queryCache'
 export { setFocusHandler } from './setFocusHandler'
 export { setOnlineHandler } from './setOnlineHandler'
 export { CancelledError, isCancelledError, isError, setConsole } from './utils'
@@ -7,5 +12,4 @@ export { CancelledError, isCancelledError, isError, setConsole } from './utils'
 // Types
 export * from './types'
 export type { Query } from './query'
-export type { QueryCache } from './queryCache'
 export type { ConsoleObject } from './utils'
diff --git a/src/core/query.ts b/src/core/query.ts
index b8d50fa30..87191f446 100644
--- a/src/core/query.ts
+++ b/src/core/query.ts
@@ -34,6 +34,7 @@ export interface QueryState<TResult, TError> {
   isFetching: boolean
   isFetchingMore: IsFetchingMoreValue
   isInitialData: boolean
+  isInvalidated: boolean
   status: QueryStatus
   throwInErrorBoundary?: boolean
   updateCount: number
@@ -58,6 +59,7 @@ const enum ActionType {
   Fetch,
   Success,
   Error,
+  Invalidate,
 }
 
 interface SetDataOptions {
@@ -85,10 +87,15 @@ interface ErrorAction<TError> {
   error: TError
 }
 
+interface InvalidateAction {
+  type: ActionType.Invalidate
+}
+
 export type Action<TResult, TError> =
   | ErrorAction<TError>
   | FailedAction
   | FetchAction
+  | InvalidateAction
   | SuccessAction<TResult>
 
 // CLASS
@@ -146,7 +153,7 @@ export class Query<TResult, TError> {
     }
 
     this.gcTimeout = setTimeout(() => {
-      this.clear()
+      this.remove()
     }, this.cacheTime)
   }
 
@@ -206,7 +213,17 @@ export class Query<TResult, TError> {
     })
   }
 
+  /**
+   * @deprecated
+   */
   clear(): void {
+    Console.warn(
+      'react-query: clear() has been deprecated, please use remove() instead'
+    )
+    this.remove()
+  }
+
+  remove(): void {
     this.queryCache.removeQuery(this)
   }
 
@@ -216,16 +233,21 @@ export class Query<TResult, TError> {
     this.cancel()
   }
 
-  isEnabled(): boolean {
+  isActive(): boolean {
     return this.observers.some(observer => observer.config.enabled)
   }
 
   isStale(): boolean {
-    return this.observers.some(observer => observer.getCurrentResult().isStale)
+    return (
+      this.state.isInvalidated ||
+      this.state.status !== QueryStatus.Success ||
+      this.observers.some(observer => observer.getCurrentResult().isStale)
+    )
   }
 
   isStaleByTime(staleTime = 0): boolean {
     return (
+      this.state.isInvalidated ||
       this.state.status !== QueryStatus.Success ||
       this.state.updatedAt + staleTime <= Date.now()
     )
@@ -285,6 +307,12 @@ export class Query<TResult, TError> {
     }
   }
 
+  invalidate(): void {
+    if (!this.state.isInvalidated) {
+      this.dispatch({ type: ActionType.Invalidate })
+    }
+  }
+
   async refetch(
     options?: RefetchOptions,
     config?: ResolvedQueryConfig<TResult, TError>
@@ -600,6 +628,7 @@ function getDefaultState<TResult, TError>(
     isFetching: status === QueryStatus.Loading,
     isFetchingMore: false,
     isInitialData: true,
+    isInvalidated: false,
     status,
     updateCount: 0,
     updatedAt: Date.now(),
@@ -637,6 +666,7 @@ export function queryReducer<TResult, TError>(
         isFetching: false,
         isFetchingMore: false,
         isInitialData: false,
+        isInvalidated: false,
         status: QueryStatus.Success,
         updateCount: state.updateCount + 1,
         updatedAt: action.updatedAt ?? Date.now(),
@@ -652,6 +682,11 @@ export function queryReducer<TResult, TError>(
         throwInErrorBoundary: true,
         updateCount: state.updateCount + 1,
       }
+    case ActionType.Invalidate:
+      return {
+        ...state,
+        isInvalidated: true,
+      }
     default:
       return state
   }
diff --git a/src/core/queryCache.ts b/src/core/queryCache.ts
index c4eb38ab6..8853efc91 100644
--- a/src/core/queryCache.ts
+++ b/src/core/queryCache.ts
@@ -3,8 +3,8 @@ import {
   deepIncludes,
   getQueryArgs,
   isDocumentVisible,
-  isPlainObject,
   isOnline,
+  isPlainObject,
   isServer,
 } from './utils'
 import { getResolvedQueryConfig } from './config'
@@ -35,14 +35,19 @@ interface PrefetchQueryOptions {
   throwOnError?: boolean
 }
 
-interface InvalidateQueriesOptions extends QueryPredicateOptions {
+interface RefetchQueriesOptions extends QueryPredicateOptions {
+  throwOnError?: boolean
+}
+
+interface InvalidateQueriesOptions extends RefetchQueriesOptions {
   refetchActive?: boolean
   refetchInactive?: boolean
-  throwOnError?: boolean
 }
 
 interface QueryPredicateOptions {
+  active?: boolean
   exact?: boolean
+  stale?: boolean
 }
 
 type QueryPredicate = QueryKey | QueryPredicateFn | true
@@ -123,7 +128,7 @@ export class QueryCache {
     predicate?: QueryPredicate,
     options?: QueryPredicateOptions
   ): Query<TResult, TError>[] {
-    if (predicate === true || typeof predicate === 'undefined') {
+    if (!options && (predicate === true || typeof predicate === 'undefined')) {
       return this.queriesArray
     }
 
@@ -134,10 +139,19 @@ export class QueryCache {
     } else {
       const resolvedConfig = this.getResolvedQueryConfig(predicate)
 
-      predicateFn = d =>
-        options?.exact
-          ? d.queryHash === resolvedConfig.queryHash
-          : deepIncludes(d.queryKey, resolvedConfig.queryKey)
+      predicateFn = query => {
+        if (
+          options &&
+          ((options.exact && query.queryHash !== resolvedConfig.queryHash) ||
+            (typeof options.active === 'boolean' &&
+              query.isActive() !== options.active) ||
+            (typeof options.stale === 'boolean' &&
+              query.isStale() !== options.stale))
+        ) {
+          return false
+        }
+        return deepIncludes(query.queryKey, resolvedConfig.queryKey)
+      }
     }
 
     return this.queriesArray.filter(predicateFn)
@@ -186,30 +200,62 @@ export class QueryCache {
     })
   }
 
-  async invalidateQueries(
+  /**
+   * @return Promise resolving to an array with the invalidated queries.
+   */
+  invalidateQueries(
     predicate?: QueryPredicate,
     options?: InvalidateQueriesOptions
-  ): Promise<void> {
-    const { refetchActive = true, refetchInactive = false, throwOnError } =
-      options || {}
+  ): Promise<Query<unknown, unknown>[]> {
+    const queries = this.getQueries(predicate, options)
 
-    try {
-      await Promise.all(
-        this.getQueries(predicate, options).map(query => {
-          const enabled = query.isEnabled()
-
-          if ((enabled && refetchActive) || (!enabled && refetchInactive)) {
-            return query.fetch()
-          }
-
-          return undefined
-        })
-      )
-    } catch (err) {
-      if (throwOnError) {
-        throw err
-      }
+    queries.forEach(query => {
+      query.invalidate()
+    })
+
+    const { refetchActive = true, refetchInactive = false } = options || {}
+
+    if (!refetchInactive && !refetchActive) {
+      return Promise.resolve(queries)
+    }
+
+    const refetchOptions: RefetchQueriesOptions = { ...options }
+
+    if (refetchActive && !refetchInactive) {
+      refetchOptions.active = true
+    } else if (refetchInactive && !refetchActive) {
+      refetchOptions.active = false
     }
+
+    let promise = this.refetchQueries(predicate, refetchOptions)
+
+    if (!options?.throwOnError) {
+      promise = promise.catch(() => queries)
+    }
+
+    return promise.then(() => queries)
+  }
+
+  /**
+   * @return Promise resolving to an array with the refetched queries.
+   */
+  refetchQueries(
+    predicate?: QueryPredicate,
+    options?: RefetchQueriesOptions
+  ): Promise<Query<unknown, unknown>[]> {
+    const promises: Promise<Query<unknown, unknown>>[] = []
+
+    this.getQueries(predicate, options).forEach(query => {
+      let promise = query.fetch().then(() => query)
+
+      if (!options?.throwOnError) {
+        promise = promise.catch(() => query)
+      }
+
+      promises.push(promise)
+    })
+
+    return Promise.all(promises)
   }
 
   resetErrorBoundaries(): void {
@@ -339,10 +385,13 @@ export class QueryCache {
   }
 }
 
-const defaultQueryCache = makeQueryCache({ frozen: isServer })
+const defaultQueryCache = new QueryCache({ frozen: isServer })
 export { defaultQueryCache as queryCache }
 export const queryCaches = [defaultQueryCache]
 
+/**
+ * @deprecated
+ */
 export function makeQueryCache(config?: QueryCacheConfig) {
   return new QueryCache(config)
 }
diff --git a/src/core/queryObserver.ts b/src/core/queryObserver.ts
index cbe1366f9..e88b650b7 100644
--- a/src/core/queryObserver.ts
+++ b/src/core/queryObserver.ts
@@ -30,7 +30,7 @@ export class QueryObserver<TResult, TError> {
     this.initialUpdateCount = 0
 
     // Bind exposed methods
-    this.clear = this.clear.bind(this)
+    this.remove = this.remove.bind(this)
     this.refetch = this.refetch.bind(this)
     this.fetchMore = this.fetchMore.bind(this)
 
@@ -110,8 +110,15 @@ export class QueryObserver<TResult, TError> {
     return this.currentResult
   }
 
+  /**
+   * @deprecated
+   */
   clear(): void {
-    return this.currentQuery.clear()
+    this.remove()
+  }
+
+  remove(): void {
+    this.currentQuery.remove()
   }
 
   refetch(options?: RefetchOptions): Promise<TResult | undefined> {
@@ -234,7 +241,7 @@ export class QueryObserver<TResult, TError> {
     this.currentResult = {
       ...getStatusProps(status),
       canFetchMore: state.canFetchMore,
-      clear: this.clear,
+      clear: this.remove,
       data,
       error: state.error,
       failureCount: state.failureCount,
@@ -247,6 +254,7 @@ export class QueryObserver<TResult, TError> {
       isPreviousData,
       isStale: this.isStale,
       refetch: this.refetch,
+      remove: this.remove,
       updatedAt,
     }
   }
@@ -298,8 +306,8 @@ export class QueryObserver<TResult, TError> {
     const { config } = this
     const { type } = action
 
-    // Update stale state on success or error
-    if (type === 2 || type === 3) {
+    // Update stale state on success, error or invalidation
+    if (type === 2 || type === 3 || type === 4) {
       this.isStale = this.currentQuery.isStaleByTime(config.staleTime)
     }
 
@@ -308,15 +316,23 @@ export class QueryObserver<TResult, TError> {
     this.updateResult()
     const currentResult = this.currentResult
 
-    // Trigger callbacks and timers on success or error
+    // Update timers on success, error or invalidation
+    if (type === 2 || type === 3 || type === 4) {
+      this.updateTimers()
+    }
+
+    // Trigger callbacks on success or error
     if (type === 2) {
       config.onSuccess?.(currentResult.data!)
       config.onSettled?.(currentResult.data!, null)
-      this.updateTimers()
     } else if (type === 3) {
       config.onError?.(currentResult.error!)
       config.onSettled?.(undefined, currentResult.error!)
-      this.updateTimers()
+    }
+
+    // Do not notify if the query was invalidated but the stale state did not changed
+    if (type === 4 && currentResult.isStale === prevResult.isStale) {
+      return
     }
 
     if (
diff --git a/src/core/types.ts b/src/core/types.ts
index 0f3f636d0..e86030300 100644
--- a/src/core/types.ts
+++ b/src/core/types.ts
@@ -207,6 +207,7 @@ export interface QueryResultBase<TResult, TError = unknown> {
   isStale: boolean
   isSuccess: boolean
   refetch: (options?: RefetchOptions) => Promise<TResult | undefined>
+  remove: () => void
   status: QueryStatus
   updatedAt: number
 }
diff --git a/src/react/ReactQueryCacheProvider.tsx b/src/react/ReactQueryCacheProvider.tsx
index aa7541a56..90b6ea498 100644
--- a/src/react/ReactQueryCacheProvider.tsx
+++ b/src/react/ReactQueryCacheProvider.tsx
@@ -1,11 +1,10 @@
 import React from 'react'
 
 import {
+  QueryCache,
   queryCache as defaultQueryCache,
   queryCaches,
-  makeQueryCache,
 } from '../core'
-import { QueryCache } from '../core/queryCache'
 
 const queryCacheContext = React.createContext(defaultQueryCache)
 
@@ -20,7 +19,7 @@ export const ReactQueryCacheProvider: React.FC<ReactQueryCacheProviderProps> = (
   children,
 }) => {
   const resolvedQueryCache = React.useMemo(
-    () => queryCache || makeQueryCache(),
+    () => queryCache || new QueryCache(),
     [queryCache]
   )
 
diff --git a/src/react/ReactQueryErrorResetBoundary.tsx b/src/react/ReactQueryErrorResetBoundary.tsx
index ac6a8adf9..af5edb39f 100644
--- a/src/react/ReactQueryErrorResetBoundary.tsx
+++ b/src/react/ReactQueryErrorResetBoundary.tsx
@@ -9,7 +9,7 @@ interface ReactQueryErrorResetBoundaryValue {
 }
 
 function createValue(): ReactQueryErrorResetBoundaryValue {
-  let isReset = true
+  let isReset = false
   return {
     clearReset: () => {
       isReset = false


Test patch:
diff --git a/src/core/tests/queryCache.test.tsx b/src/core/tests/queryCache.test.tsx
index 5f56aa288..e0837cfc8 100644
--- a/src/core/tests/queryCache.test.tsx
+++ b/src/core/tests/queryCache.test.tsx
@@ -5,7 +5,7 @@ import {
   mockConsoleError,
   mockNavigatorOnLine,
 } from '../../react/tests/utils'
-import { makeQueryCache, queryCache as defaultQueryCache } from '..'
+import { queryCache as defaultQueryCache, QueryCache } from '..'
 import { isCancelledError, isError } from '../utils'
 
 describe('queryCache', () => {
@@ -257,6 +257,37 @@ describe('queryCache', () => {
     expect(data).toEqual(['data1', 'data2'])
   })
 
+  test('getQueries should filter correctly', async () => {
+    const key1 = queryKey()
+    const key2 = queryKey()
+    const cache = defaultQueryCache
+
+    await cache.prefetchQuery(key1, () => 'data1')
+    await cache.prefetchQuery(key2, () => 'data2')
+    await cache.invalidateQueries(key2)
+    const query1 = cache.getQuery(key1)!
+    const query2 = cache.getQuery(key2)!
+
+    expect(cache.getQueries(key1)).toEqual([query1])
+    expect(cache.getQueries(key1, {})).toEqual([query1])
+    expect(cache.getQueries(key1, { active: false })).toEqual([query1])
+    expect(cache.getQueries(key1, { active: true })).toEqual([])
+    expect(cache.getQueries(key1, { stale: true })).toEqual([])
+    expect(cache.getQueries(key1, { stale: false })).toEqual([query1])
+    expect(cache.getQueries(key1, { stale: false, active: true })).toEqual([])
+    expect(cache.getQueries(key1, { stale: false, active: false })).toEqual([
+      query1,
+    ])
+    expect(
+      cache.getQueries(key1, { stale: false, active: false, exact: true })
+    ).toEqual([query1])
+
+    expect(cache.getQueries(key2)).toEqual([query2])
+    expect(cache.getQueries(key2, { stale: undefined })).toEqual([query2])
+    expect(cache.getQueries(key2, { stale: true })).toEqual([query2])
+    expect(cache.getQueries(key2, { stale: false })).toEqual([])
+  })
+
   test('query interval is cleared when unsubscribed to a refetchInterval query', async () => {
     const key = queryKey()
 
@@ -308,12 +339,12 @@ describe('queryCache', () => {
     expect(defaultQueryCache.getQuery(key)).toBeDefined()
   })
 
-  describe('makeQueryCache', () => {
+  describe('QueryCache', () => {
     test('merges defaultConfig so providing a queryFn does not overwrite the default queryKeySerializerFn', async () => {
       const key = queryKey()
 
       const queryFn = () => 'data'
-      const queryCache = makeQueryCache({
+      const queryCache = new QueryCache({
         defaultConfig: { queries: { queryFn } },
       })
 
@@ -325,7 +356,7 @@ describe('queryCache', () => {
     test('merges defaultConfig when query is added to cache', async () => {
       const key = queryKey()
 
-      const queryCache = makeQueryCache({
+      const queryCache = new QueryCache({
         defaultConfig: {
           queries: { refetchOnMount: false, staleTime: Infinity },
         },
@@ -341,7 +372,7 @@ describe('queryCache', () => {
     test('built queries are referencing the correct queryCache', () => {
       const key = queryKey()
 
-      const queryCache = makeQueryCache()
+      const queryCache = new QueryCache()
       const query = queryCache.buildQuery(key)
 
       // @ts-expect-error
@@ -351,7 +382,7 @@ describe('queryCache', () => {
     test('notifyGlobalListeners passes the same instance', () => {
       const key = queryKey()
 
-      const queryCache = makeQueryCache()
+      const queryCache = new QueryCache()
       const subscriber = jest.fn()
       const unsubscribe = queryCache.subscribe(subscriber)
       const query = queryCache.buildQuery(key)
diff --git a/src/hydration/tests/hydration.test.tsx b/src/hydration/tests/hydration.test.tsx
index e30b1862a..3dd89bc91 100644
--- a/src/hydration/tests/hydration.test.tsx
+++ b/src/hydration/tests/hydration.test.tsx
@@ -1,5 +1,5 @@
 import { sleep } from '../../react/tests/utils'
-import { makeQueryCache } from '../..'
+import { QueryCache } from '../..'
 import { dehydrate, hydrate } from '../hydration'
 
 const fetchData: <TResult>(
@@ -12,7 +12,7 @@ const fetchData: <TResult>(
 
 describe('dehydration and rehydration', () => {
   test('should work with serializeable values', async () => {
-    const queryCache = makeQueryCache()
+    const queryCache = new QueryCache()
     await queryCache.prefetchQuery('string', () => fetchData('string'))
     await queryCache.prefetchQuery('number', () => fetchData(1))
     await queryCache.prefetchQuery('boolean', () => fetchData(true))
@@ -27,7 +27,7 @@ describe('dehydration and rehydration', () => {
     // ---
 
     const parsed = JSON.parse(stringified)
-    const hydrationQueryCache = makeQueryCache()
+    const hydrationQueryCache = new QueryCache()
     hydrate(hydrationQueryCache, parsed)
     expect(hydrationQueryCache.getQuery('string')?.state.data).toBe('string')
     expect(hydrationQueryCache.getQuery('number')?.state.data).toBe(1)
@@ -69,7 +69,7 @@ describe('dehydration and rehydration', () => {
   })
 
   test('should schedule garbage collection, measured from hydration', async () => {
-    const queryCache = makeQueryCache()
+    const queryCache = new QueryCache()
     await queryCache.prefetchQuery('string', () => fetchData('string'), {
       cacheTime: 50,
     })
@@ -81,7 +81,7 @@ describe('dehydration and rehydration', () => {
     // ---
 
     const parsed = JSON.parse(stringified)
-    const hydrationQueryCache = makeQueryCache()
+    const hydrationQueryCache = new QueryCache()
     hydrate(hydrationQueryCache, parsed)
     expect(hydrationQueryCache.getQuery('string')?.state.data).toBe('string')
     await sleep(40)
@@ -94,7 +94,7 @@ describe('dehydration and rehydration', () => {
   })
 
   test('should work with complex keys', async () => {
-    const queryCache = makeQueryCache()
+    const queryCache = new QueryCache()
     await queryCache.prefetchQuery(
       ['string', { key: ['string'], key2: 0 }],
       () => fetchData('string')
@@ -105,7 +105,7 @@ describe('dehydration and rehydration', () => {
     // ---
 
     const parsed = JSON.parse(stringified)
-    const hydrationQueryCache = makeQueryCache()
+    const hydrationQueryCache = new QueryCache()
     hydrate(hydrationQueryCache, parsed)
     expect(
       hydrationQueryCache.getQuery(['string', { key: ['string'], key2: 0 }])
@@ -128,7 +128,7 @@ describe('dehydration and rehydration', () => {
     const consoleMock = jest.spyOn(console, 'error')
     consoleMock.mockImplementation(() => undefined)
 
-    const queryCache = makeQueryCache()
+    const queryCache = new QueryCache()
     await queryCache.prefetchQuery('success', () => fetchData('success'))
     queryCache.prefetchQuery('loading', () => fetchData('loading', 10000))
     await queryCache.prefetchQuery('error', () => {
@@ -140,7 +140,7 @@ describe('dehydration and rehydration', () => {
     // ---
 
     const parsed = JSON.parse(stringified)
-    const hydrationQueryCache = makeQueryCache()
+    const hydrationQueryCache = new QueryCache()
     hydrate(hydrationQueryCache, parsed)
 
     expect(hydrationQueryCache.getQuery('success')).toBeTruthy()
@@ -153,7 +153,7 @@ describe('dehydration and rehydration', () => {
   })
 
   test('should filter queries via shouldDehydrate', async () => {
-    const queryCache = makeQueryCache()
+    const queryCache = new QueryCache()
     await queryCache.prefetchQuery('string', () => fetchData('string'))
     await queryCache.prefetchQuery('number', () => fetchData(1))
     const dehydrated = dehydrate(queryCache, {
@@ -172,7 +172,7 @@ describe('dehydration and rehydration', () => {
     // ---
 
     const parsed = JSON.parse(stringified)
-    const hydrationQueryCache = makeQueryCache()
+    const hydrationQueryCache = new QueryCache()
     hydrate(hydrationQueryCache, parsed)
     expect(hydrationQueryCache.getQuery('string')).toBeUndefined()
     expect(hydrationQueryCache.getQuery('number')?.state.data).toBe(1)
@@ -182,7 +182,7 @@ describe('dehydration and rehydration', () => {
   })
 
   test('should not overwrite query in cache if hydrated query is older', async () => {
-    const queryCache = makeQueryCache()
+    const queryCache = new QueryCache()
     await queryCache.prefetchQuery('string', () => fetchData('string-older', 5))
     const dehydrated = dehydrate(queryCache)
     const stringified = JSON.stringify(dehydrated)
@@ -190,7 +190,7 @@ describe('dehydration and rehydration', () => {
     // ---
 
     const parsed = JSON.parse(stringified)
-    const hydrationQueryCache = makeQueryCache()
+    const hydrationQueryCache = new QueryCache()
     await hydrationQueryCache.prefetchQuery('string', () =>
       fetchData('string-newer', 5)
     )
@@ -205,14 +205,14 @@ describe('dehydration and rehydration', () => {
   })
 
   test('should overwrite query in cache if hydrated query is newer', async () => {
-    const hydrationQueryCache = makeQueryCache()
+    const hydrationQueryCache = new QueryCache()
     await hydrationQueryCache.prefetchQuery('string', () =>
       fetchData('string-older', 5)
     )
 
     // ---
 
-    const queryCache = makeQueryCache()
+    const queryCache = new QueryCache()
     await queryCache.prefetchQuery('string', () => fetchData('string-newer', 5))
     const dehydrated = dehydrate(queryCache)
     const stringified = JSON.stringify(dehydrated)
diff --git a/src/hydration/tests/react.test.tsx b/src/hydration/tests/react.test.tsx
index 9d50d4e0d..a52e8a837 100644
--- a/src/hydration/tests/react.test.tsx
+++ b/src/hydration/tests/react.test.tsx
@@ -1,7 +1,7 @@
 import React from 'react'
 import { render } from '@testing-library/react'
 
-import { ReactQueryCacheProvider, makeQueryCache, useQuery } from '../..'
+import { ReactQueryCacheProvider, QueryCache, useQuery } from '../..'
 import { dehydrate, useHydrate, Hydrate } from '../'
 import { waitForMs } from '../../react/tests/utils'
 
@@ -12,7 +12,7 @@ describe('React hydration', () => {
   let stringifiedState: string
 
   beforeAll(async () => {
-    const serverQueryCache = makeQueryCache()
+    const serverQueryCache = new QueryCache()
     await serverQueryCache.prefetchQuery('string', dataQuery)
     const dehydrated = dehydrate(serverQueryCache)
     stringifiedState = JSON.stringify(dehydrated)
@@ -41,7 +41,7 @@ describe('React hydration', () => {
 
     test('should hydrate queries to the cache on context', async () => {
       const dehydratedState = JSON.parse(stringifiedState)
-      const clientQueryCache = makeQueryCache()
+      const clientQueryCache = new QueryCache()
 
       function Page() {
         useHydrate(dehydratedState)
@@ -68,7 +68,7 @@ describe('React hydration', () => {
   describe('ReactQueryCacheProvider with hydration support', () => {
     test('should hydrate new queries if queries change', async () => {
       const dehydratedState = JSON.parse(stringifiedState)
-      const clientQueryCache = makeQueryCache()
+      const clientQueryCache = new QueryCache()
 
       function Page({ queryKey }: { queryKey: string }) {
         const { data } = useQuery(queryKey, dataQuery)
@@ -90,7 +90,7 @@ describe('React hydration', () => {
       await waitForMs(10)
       rendered.getByText('string')
 
-      const intermediateCache = makeQueryCache()
+      const intermediateCache = new QueryCache()
       await intermediateCache.prefetchQuery('string', () =>
         dataQuery('should change')
       )
@@ -119,7 +119,7 @@ describe('React hydration', () => {
 
     test('should hydrate queries to new cache if cache changes', async () => {
       const dehydratedState = JSON.parse(stringifiedState)
-      const clientQueryCache = makeQueryCache()
+      const clientQueryCache = new QueryCache()
 
       function Page() {
         const { data } = useQuery('string', dataQuery)
@@ -141,7 +141,7 @@ describe('React hydration', () => {
       await waitForMs(10)
       rendered.getByText('string')
 
-      const newClientQueryCache = makeQueryCache()
+      const newClientQueryCache = new QueryCache()
 
       rendered.rerender(
         <ReactQueryCacheProvider queryCache={newClientQueryCache}>
diff --git a/src/hydration/tests/ssr.test.tsx b/src/hydration/tests/ssr.test.tsx
index 5d83153dc..135de3f53 100644
--- a/src/hydration/tests/ssr.test.tsx
+++ b/src/hydration/tests/ssr.test.tsx
@@ -4,10 +4,10 @@ import ReactDOMServer from 'react-dom/server'
 import { waitFor } from '@testing-library/react'
 
 import {
-  makeQueryCache,
   useQuery,
   setConsole,
   ReactQueryCacheProvider,
+  QueryCache,
 } from '../..'
 import { dehydrate, Hydrate } from '../'
 import * as utils from '../../core/utils'
@@ -71,7 +71,7 @@ describe('Server side rendering with de/rehydration', () => {
     // -- Server part --
     setIsServer(true)
 
-    const serverPrefetchCache = makeQueryCache()
+    const serverPrefetchCache = new QueryCache()
     const prefetchPromise = serverPrefetchCache.prefetchQuery('success', () =>
       fetchDataSuccess('success')
     )
@@ -141,7 +141,7 @@ describe('Server side rendering with de/rehydration', () => {
 
     // -- Server part --
     setIsServer(true)
-    const serverQueryCache = makeQueryCache()
+    const serverQueryCache = new QueryCache()
     const prefetchPromise = serverQueryCache.prefetchQuery('error', () =>
       fetchDataError()
     )
@@ -215,7 +215,7 @@ describe('Server side rendering with de/rehydration', () => {
     // -- Server part --
     setIsServer(true)
 
-    const serverPrefetchCache = makeQueryCache()
+    const serverPrefetchCache = new QueryCache()
     const dehydratedStateServer = dehydrate(serverPrefetchCache)
     const markup = ReactDOMServer.renderToString(
       <ReactQueryCacheProvider>
diff --git a/src/react/tests/ReactQueryCacheProvider.test.tsx b/src/react/tests/ReactQueryCacheProvider.test.tsx
index 20e6c8e31..95e2137c3 100644
--- a/src/react/tests/ReactQueryCacheProvider.test.tsx
+++ b/src/react/tests/ReactQueryCacheProvider.test.tsx
@@ -3,7 +3,7 @@ import { render, waitFor } from '@testing-library/react'
 
 import { sleep, queryKey } from './utils'
 import { ReactQueryCacheProvider, useQuery, useQueryCache } from '..'
-import { makeQueryCache, queryCache, QueryCache } from '../../core'
+import { queryCache, QueryCache } from '../../core'
 
 describe('ReactQueryCacheProvider', () => {
   test('when not used, falls back to global cache', async () => {
@@ -32,7 +32,7 @@ describe('ReactQueryCacheProvider', () => {
   test('sets a specific cache for all queries to use', async () => {
     const key = queryKey()
 
-    const cache = makeQueryCache()
+    const cache = new QueryCache()
 
     function Page() {
       const { data } = useQuery(key, async () => {
@@ -90,8 +90,8 @@ describe('ReactQueryCacheProvider', () => {
     const key1 = queryKey()
     const key2 = queryKey()
 
-    const cache1 = makeQueryCache()
-    const cache2 = makeQueryCache()
+    const cache1 = new QueryCache()
+    const cache2 = new QueryCache()
 
     function Page1() {
       const { data } = useQuery(key1, async () => {
@@ -145,7 +145,7 @@ describe('ReactQueryCacheProvider', () => {
     const key = queryKey()
 
     const caches: QueryCache[] = []
-    const customCache = makeQueryCache()
+    const customCache = new QueryCache()
 
     function Page() {
       const contextCache = useQueryCache()
@@ -194,7 +194,7 @@ describe('ReactQueryCacheProvider', () => {
   test("uses defaultConfig for queries when they don't provide their own config", async () => {
     const key = queryKey()
 
-    const cache = makeQueryCache({
+    const cache = new QueryCache({
       defaultConfig: {
         queries: {
           staleTime: Infinity,
diff --git a/src/react/tests/ReactQueryResetErrorBoundary.test.tsx b/src/react/tests/ReactQueryResetErrorBoundary.test.tsx
new file mode 100644
index 000000000..c70891448
--- /dev/null
+++ b/src/react/tests/ReactQueryResetErrorBoundary.test.tsx
@@ -0,0 +1,66 @@
+import { render, waitFor, fireEvent } from '@testing-library/react'
+import { ErrorBoundary } from 'react-error-boundary'
+import * as React from 'react'
+
+import { sleep, queryKey, mockConsoleError } from './utils'
+import { useQuery, ReactQueryErrorResetBoundary } from '../..'
+
+describe('ReactQueryResetErrorBoundary', () => {
+  it('should retry fetch if the reset error boundary has been reset', async () => {
+    const key = queryKey()
+
+    let succeed = false
+    const consoleMock = mockConsoleError()
+
+    function Page() {
+      const { data } = useQuery(
+        key,
+        async () => {
+          await sleep(10)
+          if (!succeed) {
+            throw new Error('Error')
+          } else {
+            return 'data'
+          }
+        },
+        {
+          retry: false,
+          useErrorBoundary: true,
+        }
+      )
+      return <div>{data}</div>
+    }
+
+    const rendered = render(
+      <ReactQueryErrorResetBoundary>
+        {({ reset }) => (
+          <ErrorBoundary
+            onReset={reset}
+            fallbackRender={({ resetErrorBoundary }) => (
+              <div>
+                <div>error boundary</div>
+                <button
+                  onClick={() => {
+                    resetErrorBoundary()
+                  }}
+                >
+                  retry
+                </button>
+              </div>
+            )}
+          >
+            <Page />
+          </ErrorBoundary>
+        )}
+      </ReactQueryErrorResetBoundary>
+    )
+
+    await waitFor(() => rendered.getByText('error boundary'))
+    await waitFor(() => rendered.getByText('retry'))
+    succeed = true
+    fireEvent.click(rendered.getByText('retry'))
+    await waitFor(() => rendered.getByText('data'))
+
+    consoleMock.mockRestore()
+  })
+})
diff --git a/src/react/tests/ssr.test.tsx b/src/react/tests/ssr.test.tsx
index 5178d1724..a42443494 100644
--- a/src/react/tests/ssr.test.tsx
+++ b/src/react/tests/ssr.test.tsx
@@ -8,7 +8,7 @@ import { renderToString } from 'react-dom/server'
 
 import { sleep, queryKey } from './utils'
 import { usePaginatedQuery, ReactQueryCacheProvider, useQuery } from '..'
-import { queryCache, makeQueryCache } from '../../core'
+import { queryCache, QueryCache } from '../../core'
 
 describe('Server Side Rendering', () => {
   // A frozen cache does not cache any data. This is the default
@@ -28,13 +28,13 @@ describe('Server Side Rendering', () => {
   })
 
   // When consumers of the library create a cache explicitly by
-  // calling makeQueryCache, they take on the responsibility of
+  // creating a QueryCache, they take on the responsibility of
   // not using that cache to cache data between requests or do so
   // in a safe way.
   it('created caches should be unfrozen by default', async () => {
     const key = queryKey()
 
-    const cache = makeQueryCache()
+    const cache = new QueryCache()
     const fetchFn = () => Promise.resolve('data')
     const data = await cache.prefetchQuery(key, fetchFn)
 
@@ -46,7 +46,7 @@ describe('Server Side Rendering', () => {
     it('should not trigger fetch', () => {
       const key = queryKey()
 
-      const cache = makeQueryCache({ frozen: true })
+      const cache = new QueryCache({ frozen: true })
       const queryFn = jest.fn()
 
       function Page() {
@@ -74,7 +74,7 @@ describe('Server Side Rendering', () => {
     it('should not add initialData to the cache', () => {
       const key = queryKey()
 
-      const cache = makeQueryCache({ frozen: true })
+      const cache = new QueryCache({ frozen: true })
 
       function Page() {
         const [page, setPage] = React.useState(1)
@@ -102,7 +102,7 @@ describe('Server Side Rendering', () => {
     it('should not add prefetched data to the cache', async () => {
       const key = queryKey()
 
-      const cache = makeQueryCache({ frozen: true })
+      const cache = new QueryCache({ frozen: true })
       const fetchFn = () => Promise.resolve('data')
       const data = await cache.prefetchQuery(key, fetchFn)
 
@@ -115,7 +115,7 @@ describe('Server Side Rendering', () => {
     it('should not trigger fetch', () => {
       const key = queryKey()
 
-      const cache = makeQueryCache({ frozen: false })
+      const cache = new QueryCache({ frozen: false })
       const queryFn = jest.fn()
 
       function Page() {
@@ -143,7 +143,7 @@ describe('Server Side Rendering', () => {
     it('should add prefetched data to cache', async () => {
       const key = queryKey()
 
-      const cache = makeQueryCache({ frozen: false })
+      const cache = new QueryCache({ frozen: false })
       const fetchFn = () => Promise.resolve('data')
       const data = await cache.prefetchQuery(key, fetchFn)
 
@@ -154,7 +154,7 @@ describe('Server Side Rendering', () => {
     it('should return existing data from the cache', async () => {
       const key = queryKey()
 
-      const cache = makeQueryCache({ frozen: false })
+      const cache = new QueryCache({ frozen: false })
       const queryFn = jest.fn(() => sleep(10))
 
       function Page() {
@@ -184,7 +184,7 @@ describe('Server Side Rendering', () => {
     it('should add initialData to the cache', () => {
       const key = queryKey()
 
-      const cache = makeQueryCache({ frozen: false })
+      const cache = new QueryCache({ frozen: false })
       function Page() {
         const [page, setPage] = React.useState(1)
         const { resolvedData } = usePaginatedQuery(
@@ -220,7 +220,7 @@ describe('Server Side Rendering', () => {
       // @ts-ignore
       const setTimeoutMock = jest.spyOn(global, 'setTimeout')
 
-      const cache = makeQueryCache({ frozen: false })
+      const cache = new QueryCache({ frozen: false })
       const queryFn = jest.fn(() => Promise.resolve())
 
       function Page() {
diff --git a/src/react/tests/useInfiniteQuery.test.tsx b/src/react/tests/useInfiniteQuery.test.tsx
index a85f536d7..ef3d417ca 100644
--- a/src/react/tests/useInfiniteQuery.test.tsx
+++ b/src/react/tests/useInfiniteQuery.test.tsx
@@ -78,6 +78,7 @@ describe('useInfiniteQuery', () => {
       isStale: true,
       isSuccess: false,
       refetch: expect.any(Function),
+      remove: expect.any(Function),
       status: 'loading',
       updatedAt: expect.any(Number),
     })
@@ -107,6 +108,7 @@ describe('useInfiniteQuery', () => {
       isStale: true,
       isSuccess: true,
       refetch: expect.any(Function),
+      remove: expect.any(Function),
       status: 'success',
       updatedAt: expect.any(Number),
     })
diff --git a/src/react/tests/usePaginatedQuery.test.tsx b/src/react/tests/usePaginatedQuery.test.tsx
index 09aa22a08..1f18610d4 100644
--- a/src/react/tests/usePaginatedQuery.test.tsx
+++ b/src/react/tests/usePaginatedQuery.test.tsx
@@ -50,6 +50,7 @@ describe('usePaginatedQuery', () => {
       latestData: undefined,
       resolvedData: undefined,
       refetch: expect.any(Function),
+      remove: expect.any(Function),
       status: 'loading',
       updatedAt: expect.any(Number),
     })
@@ -75,6 +76,7 @@ describe('usePaginatedQuery', () => {
       latestData: 1,
       resolvedData: 1,
       refetch: expect.any(Function),
+      remove: expect.any(Function),
       status: 'success',
       updatedAt: expect.any(Number),
     })
diff --git a/src/react/tests/useQuery.test.tsx b/src/react/tests/useQuery.test.tsx
index 8420627a8..2a1f6b1a7 100644
--- a/src/react/tests/useQuery.test.tsx
+++ b/src/react/tests/useQuery.test.tsx
@@ -140,6 +140,7 @@ describe('useQuery', () => {
       isStale: true,
       isSuccess: false,
       refetch: expect.any(Function),
+      remove: expect.any(Function),
       status: 'loading',
       updatedAt: expect.any(Number),
     })
@@ -163,6 +164,7 @@ describe('useQuery', () => {
       isStale: true,
       isSuccess: true,
       refetch: expect.any(Function),
+      remove: expect.any(Function),
       status: 'success',
       updatedAt: expect.any(Number),
     })
@@ -216,6 +218,7 @@ describe('useQuery', () => {
       isStale: true,
       isSuccess: false,
       refetch: expect.any(Function),
+      remove: expect.any(Function),
       status: 'loading',
       updatedAt: expect.any(Number),
     })
@@ -239,6 +242,7 @@ describe('useQuery', () => {
       isStale: true,
       isSuccess: false,
       refetch: expect.any(Function),
+      remove: expect.any(Function),
       status: 'loading',
       updatedAt: expect.any(Number),
     })
@@ -262,6 +266,7 @@ describe('useQuery', () => {
       isStale: true,
       isSuccess: false,
       refetch: expect.any(Function),
+      remove: expect.any(Function),
       status: 'error',
       updatedAt: expect.any(Number),
     })
@@ -482,6 +487,52 @@ describe('useQuery', () => {
     return null
   })
 
+  it('should update query stale state when invalidated with invalidateQueries', async () => {
+    const key = queryKey()
+    const states: QueryResult<string>[] = []
+
+    function Page() {
+      const state = useQuery(key, () => 'data', { staleTime: Infinity })
+
+      states.push(state)
+
+      React.useEffect(() => {
+        setTimeout(() => {
+          queryCache.invalidateQueries(key, {
+            refetchActive: false,
+            refetchInactive: false,
+          })
+        }, 10)
+      }, [])
+
+      return null
+    }
+
+    render(<Page />)
+
+    await waitForMs(100)
+
+    expect(states.length).toBe(3)
+    expect(states[0]).toMatchObject({
+      data: undefined,
+      isFetching: true,
+      isSuccess: false,
+      isStale: true,
+    })
+    expect(states[1]).toMatchObject({
+      data: 'data',
+      isFetching: false,
+      isSuccess: true,
+      isStale: false,
+    })
+    expect(states[2]).toMatchObject({
+      data: 'data',
+      isFetching: false,
+      isSuccess: true,
+      isStale: true,
+    })
+  })
+
   it('should update disabled query when updated with invalidateQueries', async () => {
     const key = queryKey()
     const states: QueryResult<number>[] = []
@@ -511,25 +562,27 @@ describe('useQuery', () => {
 
     render(<Page />)
 
-    await waitFor(() => expect(states.length).toBe(3))
+    await waitForMs(100)
 
-    expect(states).toMatchObject([
-      {
-        data: undefined,
-        isFetching: false,
-        isSuccess: false,
-      },
-      {
-        data: undefined,
-        isFetching: true,
-        isSuccess: false,
-      },
-      {
-        data: 1,
-        isFetching: false,
-        isSuccess: true,
-      },
-    ])
+    expect(states.length).toBe(3)
+    expect(states[0]).toMatchObject({
+      data: undefined,
+      isFetching: false,
+      isSuccess: false,
+      isStale: true,
+    })
+    expect(states[1]).toMatchObject({
+      data: undefined,
+      isFetching: true,
+      isSuccess: false,
+      isStale: true,
+    })
+    expect(states[2]).toMatchObject({
+      data: 1,
+      isFetching: false,
+      isSuccess: true,
+      isStale: true,
+    })
   })
 
   it('should keep the previous data when keepPreviousData is set', async () => {
@@ -905,13 +958,11 @@ describe('useQuery', () => {
 
     render(<Page />)
 
-    await waitFor(() =>
-      expect(states).toMatchObject([
-        { isStale: true },
-        { isStale: false },
-        { isStale: true },
-      ])
-    )
+    await waitForMs(100)
+
+    expect(states[0]).toMatchObject({ isStale: true })
+    expect(states[1]).toMatchObject({ isStale: false })
+    expect(states[2]).toMatchObject({ isStale: true })
   })
 
   it('should notify query cache when a query becomes stale', async () => {