Repository: tannerlinsley/react-query
Pull request #1375 (2020-12-08 20:07:01)

feat: reset query utils
- [x] Documentation
- [x] Tests

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/1omf801ot](https://vercel.com/tannerlinsley/react-query/1omf801ot)  
✅ Preview: *In Progress*


aaronjensen: @tannerlinsley thank you for your work on this. I'll see if I can help out a bit.
aaronjensen: @tannerlinsley I added a few tests and some documentation. Let me know if you'd like me to make any tweaks. I'm also testing it out here:

https://codesandbox.io/s/react-query-3-refetch-on-error-ux360?file=/src/App.js

One thing that seems odd about it is that it does not automatically trigger a refetch. This is especially odd when using suspense because you expect the data to be present unless you set enabled: false. 

Should it refetch automatically if enabled is not false?
vercel[bot]: Deployment failed with the following error:
~~~
Invalid request: should NOT have additional property `project`.
~~~


Patch:
diff --git a/docs/src/pages/reference/QueryClient.md b/docs/src/pages/reference/QueryClient.md
index 4858476ea..4e34813ae 100644
--- a/docs/src/pages/reference/QueryClient.md
+++ b/docs/src/pages/reference/QueryClient.md
@@ -32,6 +32,7 @@ Its available methods are:
 - [`refetchQueries`](#queryclientrefetchqueries)
 - [`cancelQueries`](#queryclientcancelqueries)
 - [`removeQueries`](#queryclientremovequeries)
+- [`resetQueries`](#queryclientresetqueries)
 - [`isFetching`](#queryclientisfetching)
 - [`getDefaultOptions`](#queryclientsetdefaultoptions)
 - [`setDefaultOptions`](#queryclientgetdefaultoptions)
@@ -274,6 +275,30 @@ queryClient.removeQueries(queryKey, { exact: true })
 
 This method does not return anything
 
+## `queryClient.resetQueries`
+
+The `resetQueries` method can be used to reset queries in the cache to their
+initial state based on their query keys or any other functionally accessible
+property/state of the query.
+
+This will notify subscribers &mdash; unlike `clear`, which removes all
+subscribers &mdash; and reset the query to its pre-loaded state &mdash; unlike
+`invalidateQueries`. If a query has `initialData`, the query's data will be
+reset to that.
+
+```js
+queryClient.resetQueries(queryKey, { exact: true })
+```
+
+**Options**
+
+- `queryKey?: QueryKey`: [Query Keys](../guides/query-keys)
+- `filters?: QueryFilters`: [Query Filters](../guides/query-filters)
+
+**Returns**
+
+This method does not return anything
+
 ## `queryClient.isFetching`
 
 This `isFetching` method returns 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)
diff --git a/src/core/query.ts b/src/core/query.ts
index ff37c78cb..fec4bf4f6 100644
--- a/src/core/query.ts
+++ b/src/core/query.ts
@@ -124,6 +124,7 @@ export class Query<TData = unknown, TError = unknown, TQueryFnData = TData> {
   queryKey: QueryKey
   queryHash: string
   options!: QueryOptions<TData, TError, TQueryFnData>
+  initialState: QueryState<TData, TError>
   state: QueryState<TData, TError>
   cacheTime!: number
 
@@ -141,7 +142,8 @@ export class Query<TData = unknown, TError = unknown, TQueryFnData = TData> {
     this.cache = config.cache
     this.queryKey = config.queryKey
     this.queryHash = config.queryHash
-    this.state = config.state || this.getDefaultState(this.options)
+    this.initialState = config.state || this.getDefaultState(this.options)
+    this.state = this.initialState
     this.scheduleGc()
   }
 
@@ -220,6 +222,11 @@ export class Query<TData = unknown, TError = unknown, TQueryFnData = TData> {
     this.cancel()
   }
 
+  reset(): void {
+    this.destroy()
+    this.setState(this.initialState)
+  }
+
   isActive(): boolean {
     return this.observers.some(observer => observer.options.enabled !== false)
   }
diff --git a/src/core/queryClient.ts b/src/core/queryClient.ts
index 8161343fb..af2a30074 100644
--- a/src/core/queryClient.ts
+++ b/src/core/queryClient.ts
@@ -132,6 +132,18 @@ export class QueryClient {
     })
   }
 
+  resetQueries(filters?: QueryFilters): void
+  resetQueries(queryKey?: QueryKey, filters?: QueryFilters): void
+  resetQueries(arg1?: QueryKey | QueryFilters, arg2?: QueryFilters): void {
+    const [filters] = parseFilterArgs(arg1, arg2)
+    const queryCache = this.queryCache
+    notifyManager.batch(() => {
+      queryCache.findAll(filters).forEach(query => {
+        query.reset()
+      })
+    })
+  }
+
   cancelQueries(filters?: QueryFilters, options?: CancelOptions): Promise<void>
   cancelQueries(
     queryKey?: QueryKey,


Test patch:
diff --git a/src/core/tests/queryCache.test.tsx b/src/core/tests/queryCache.test.tsx
index 877e36c47..1470d7d7e 100644
--- a/src/core/tests/queryCache.test.tsx
+++ b/src/core/tests/queryCache.test.tsx
@@ -948,6 +948,56 @@ describe('queryCache', () => {
     expect(queryFn2).toHaveBeenCalledTimes(1)
   })
 
+  test('should notify listeners when a query is reset', async () => {
+    const key = queryKey()
+
+    const callback = jest.fn()
+
+    await queryClient.prefetchQuery(key, () => 'data')
+
+    queryCache.subscribe(callback)
+
+    queryClient.resetQueries(key)
+
+    expect(callback).toHaveBeenCalled()
+  })
+
+  test('resetQueries should reset query', async () => {
+    const key = queryKey()
+
+    await queryClient.prefetchQuery(key, () => 'data')
+
+    let state = queryClient.getQueryState(key)
+    expect(state?.data).toEqual('data')
+    expect(state?.status).toEqual('success')
+
+    queryClient.resetQueries(key)
+
+    state = queryClient.getQueryState(key)
+
+    expect(state).toBeTruthy()
+    expect(state?.data).toBeUndefined()
+    expect(state?.status).toEqual('idle')
+  })
+
+  test('resetQueries should reset query data to initial data if set', async () => {
+    const key = queryKey()
+
+    await queryClient.prefetchQuery(key, () => 'data', {
+      initialData: 'initial',
+    })
+
+    let state = queryClient.getQueryState(key)
+    expect(state?.data).toEqual('data')
+
+    queryClient.resetQueries(key)
+
+    state = queryClient.getQueryState(key)
+
+    expect(state).toBeTruthy()
+    expect(state?.data).toEqual('initial')
+  })
+
   test('find should filter correctly', async () => {
     const key = queryKey()
     const testCache = new QueryCache()