Repository: tannerlinsley/react-query (ts)

Issue:
leak in query garbage collection
**Describe the bug**

Queries that should be garbage collected because the `cacheTime` has elapsed are kept in memory forever if the query was fetching when the last observer was removed.

**To Reproduce**
https://codesandbox.io/s/youthful-neumann-rh8t6?file=/src/index.js

note:
- `cacheTime` is 2 seconds
- the query takes 5 seconds to fetch
- it's best to wait until the query has loaded once, then trigger a refetch via window focus and click the button which will unmount the query while the fetch is happening.

**Expected behavior**
queries to be garbage collected correctly

**Additional context**

I think the problem is that `optionalRemove`, once it determines that it has no observers but doesn't remove the query because it's still fetching doesn't restart the timer:

https://github.com/tannerlinsley/react-query/blob/c1ae82ba188fd5abda5e256cac070145e5941447/src/core/query.ts#L216-L220


Pull request:
fix(useQuery): cleanup queries even if they have been fetching
fixes #2946 

Patch:
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 560705202..432147811 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -148,6 +148,43 @@
         "review"
       ]
     },
+    {
+      "login": "KATT",
+      "name": "Alex Johansson",
+      "avatar_url": "https://avatars.githubusercontent.com/u/459267?v=4",
+      "profile": "https://katt.dev",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "EddyVinck",
+      "name": "Eddy",
+      "avatar_url": "https://avatars.githubusercontent.com/u/23434753?v=4",
+      "profile": "http://www.eddyvinck.nl",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "prateek3255",
+      "name": "Prateek Surana",
+      "avatar_url": "https://avatars.githubusercontent.com/u/21277179?v=4",
+      "profile": "http://prateeksurana.me",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "babycourageous",
+      "name": "Rene Dellefont",
+      "avatar_url": "https://avatars.githubusercontent.com/u/14936212?v=4",
+      "profile": "https://github.com/babycourageous",
+      "contributions": [
+        "code",
+        "doc"
+      ]
+    }
   ],
   "contributorsPerLine": 7,
   "skipCi": true
diff --git a/README.md b/README.md
index 087487d2e..cab9ac311 100644
--- a/README.md
+++ b/README.md
@@ -58,26 +58,32 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 <!-- markdownlint-disable -->
 <table>
   <tr>
-    <td align="center"><a href="https://tannerlinsley.com"><img src="https://avatars0.githubusercontent.com/u/5580297?v=4" width="100px;" alt=""/><br /><sub><b>Tanner Linsley</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=tannerlinsley" title="Code">💻</a> <a href="#ideas-tannerlinsley" title="Ideas, Planning, & Feedback">🤔</a> <a href="#example-tannerlinsley" title="Examples">💡</a> <a href="#maintenance-tannerlinsley" title="Maintenance">🚧</a> <a href="https://github.com/tannerlinsley/react-query/pulls?q=is%3Apr+reviewed-by%3Atannerlinsley" title="Reviewed Pull Requests">👀</a></td>
-    <td align="center"><a href="http://cherniavskii.com"><img src="https://avatars2.githubusercontent.com/u/13808724?v=4" width="100px;" alt=""/><br /><sub><b>Andrew Cherniavskii</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=cherniavskii" title="Code">💻</a> <a href="https://github.com/tannerlinsley/react-query/issues?q=author%3Acherniavskii" title="Bug reports">🐛</a></td>
-    <td align="center"><a href="http://twitter.com/tibotiber"><img src="https://avatars3.githubusercontent.com/u/5635553?v=4" width="100px;" alt=""/><br /><sub><b>Thibaut Tiberghien</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=tibotiber" title="Documentation">📖</a></td>
-    <td align="center"><a href="https://github.com/gargroh"><img src="https://avatars3.githubusercontent.com/u/42495927?v=4" width="100px;" alt=""/><br /><sub><b>Rohit Garg</b></sub></a><br /><a href="#tool-gargroh" title="Tools">🔧</a></td>
-    <td align="center"><a href="https://github.com/Avi98"><img src="https://avatars1.githubusercontent.com/u/26133749?v=4" width="100px;" alt=""/><br /><sub><b>Avinash</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=Avi98" title="Code">💻</a> <a href="https://github.com/tannerlinsley/react-query/issues?q=author%3AAvi98" title="Bug reports">🐛</a></td>
-    <td align="center"><a href="https://github.com/CreativeTechGuy"><img src="https://avatars1.githubusercontent.com/u/12002072?v=4" width="100px;" alt=""/><br /><sub><b>Jason O'Neill</b></sub></a><br /><a href="#maintenance-CreativeTechGuy" title="Maintenance">🚧</a> <a href="https://github.com/tannerlinsley/react-query/commits?author=CreativeTechGuy" title="Tests">⚠️</a></td>
-    <td align="center"><a href="http://fb.me/yz"><img src="https://avatars3.githubusercontent.com/u/14841421?v=4" width="100px;" alt=""/><br /><sub><b>Jack Zhao</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=bugzpodder" title="Code">💻</a> <a href="https://github.com/tannerlinsley/react-query/issues?q=author%3Abugzpodder" title="Bug reports">🐛</a></td>
+    <td align="center"><a href="https://tannerlinsley.com"><img src="https://avatars0.githubusercontent.com/u/5580297?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tanner Linsley</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=tannerlinsley" title="Code">💻</a> <a href="#ideas-tannerlinsley" title="Ideas, Planning, & Feedback">🤔</a> <a href="#example-tannerlinsley" title="Examples">💡</a> <a href="#maintenance-tannerlinsley" title="Maintenance">🚧</a> <a href="https://github.com/tannerlinsley/react-query/pulls?q=is%3Apr+reviewed-by%3Atannerlinsley" title="Reviewed Pull Requests">👀</a></td>
+    <td align="center"><a href="http://cherniavskii.com"><img src="https://avatars2.githubusercontent.com/u/13808724?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrew Cherniavskii</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=cherniavskii" title="Code">💻</a> <a href="https://github.com/tannerlinsley/react-query/issues?q=author%3Acherniavskii" title="Bug reports">🐛</a></td>
+    <td align="center"><a href="http://twitter.com/tibotiber"><img src="https://avatars3.githubusercontent.com/u/5635553?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Thibaut Tiberghien</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=tibotiber" title="Documentation">📖</a></td>
+    <td align="center"><a href="https://github.com/gargroh"><img src="https://avatars3.githubusercontent.com/u/42495927?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rohit Garg</b></sub></a><br /><a href="#tool-gargroh" title="Tools">🔧</a></td>
+    <td align="center"><a href="https://github.com/Avi98"><img src="https://avatars1.githubusercontent.com/u/26133749?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Avinash</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=Avi98" title="Code">💻</a> <a href="https://github.com/tannerlinsley/react-query/issues?q=author%3AAvi98" title="Bug reports">🐛</a></td>
+    <td align="center"><a href="https://github.com/CreativeTechGuy"><img src="https://avatars1.githubusercontent.com/u/12002072?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jason O'Neill</b></sub></a><br /><a href="#maintenance-CreativeTechGuy" title="Maintenance">🚧</a> <a href="https://github.com/tannerlinsley/react-query/commits?author=CreativeTechGuy" title="Tests">⚠️</a></td>
+    <td align="center"><a href="http://fb.me/yz"><img src="https://avatars3.githubusercontent.com/u/14841421?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jack Zhao</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=bugzpodder" title="Code">💻</a> <a href="https://github.com/tannerlinsley/react-query/issues?q=author%3Abugzpodder" title="Bug reports">🐛</a></td>
   </tr>
   <tr>
-    <td align="center"><a href="https://github.com/dpyzo0o"><img src="https://avatars1.githubusercontent.com/u/24768249?v=4" width="100px;" alt=""/><br /><sub><b>dpyzo0o</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=dpyzo0o" title="Code">💻</a></td>
-    <td align="center"><a href="https://github.com/jelteliekens"><img src="https://avatars1.githubusercontent.com/u/3418474?v=4" width="100px;" alt=""/><br /><sub><b>Jelte Liekens</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=jelteliekens" title="Code">💻</a></td>
-    <td align="center"><a href="https://github.com/jgettings"><img src="https://avatars0.githubusercontent.com/u/4183742?v=4" width="100px;" alt=""/><br /><sub><b>Jen Gettings</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=jgettings" title="Code">💻</a></td>
-    <td align="center"><a href="https://github.com/justincy"><img src="https://avatars2.githubusercontent.com/u/1037458?v=4" width="100px;" alt=""/><br /><sub><b>Justin</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=justincy" title="Code">💻</a></td>
-    <td align="center"><a href="http://www.marceloalves.com"><img src="https://avatars1.githubusercontent.com/u/216782?v=4" width="100px;" alt=""/><br /><sub><b>Marcelo Alves</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=MarceloAlves" title="Code">💻</a></td>
-    <td align="center"><a href="https://github.com/zorzysty"><img src="https://avatars0.githubusercontent.com/u/5398733?v=4" width="100px;" alt=""/><br /><sub><b>Zorza</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=zorzysty" title="Documentation">📖</a></td>
-    <td align="center"><a href="https://tkdodo.eu"><img src="https://avatars0.githubusercontent.com/u/1021430?v=4" width="100px;" alt=""/><br /><sub><b>Dominik Dorfmeister</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=tkdodo" title="Code">💻</a> <a href="https://github.com/tannerlinsley/react-query/commits?author=tkdodo" title="Documentation">📖</a> <a href="#maintenance-tkdodo" title="Maintenance">🚧</a> <a href="#question-tkdodo" title="Question">💬</a> <a href="https://github.com/tannerlinsley/react-query/pulls?q=is%3Apr+reviewed-by%3Atkdodo" title="Reviewed Pull Requests">👀</a></td>
+    <td align="center"><a href="https://github.com/dpyzo0o"><img src="https://avatars1.githubusercontent.com/u/24768249?v=4?s=100" width="100px;" alt=""/><br /><sub><b>dpyzo0o</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=dpyzo0o" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/jelteliekens"><img src="https://avatars1.githubusercontent.com/u/3418474?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jelte Liekens</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=jelteliekens" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/jgettings"><img src="https://avatars0.githubusercontent.com/u/4183742?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jen Gettings</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=jgettings" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/justincy"><img src="https://avatars2.githubusercontent.com/u/1037458?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Justin</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=justincy" title="Code">💻</a></td>
+    <td align="center"><a href="http://www.marceloalves.com"><img src="https://avatars1.githubusercontent.com/u/216782?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Marcelo Alves</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=MarceloAlves" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/zorzysty"><img src="https://avatars0.githubusercontent.com/u/5398733?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Zorza</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=zorzysty" title="Documentation">📖</a></td>
+    <td align="center"><a href="https://tkdodo.eu"><img src="https://avatars0.githubusercontent.com/u/1021430?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dominik Dorfmeister</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=tkdodo" title="Code">💻</a> <a href="https://github.com/tannerlinsley/react-query/commits?author=tkdodo" title="Documentation">📖</a> <a href="#maintenance-tkdodo" title="Maintenance">🚧</a> <a href="#question-tkdodo" title="Answering Questions">💬</a> <a href="https://github.com/tannerlinsley/react-query/pulls?q=is%3Apr+reviewed-by%3Atkdodo" title="Reviewed Pull Requests">👀</a></td>
+  </tr>
+  <tr>
+    <td align="center"><a href="https://katt.dev"><img src="https://avatars.githubusercontent.com/u/459267?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alex Johansson</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=KATT" title="Code">💻</a></td>
+    <td align="center"><a href="http://www.eddyvinck.nl"><img src="https://avatars.githubusercontent.com/u/23434753?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Eddy</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=EddyVinck" title="Code">💻</a></td>
+    <td align="center"><a href="http://prateeksurana.me"><img src="https://avatars.githubusercontent.com/u/21277179?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Prateek Surana</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=prateek3255" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/babycourageous"><img src="https://avatars.githubusercontent.com/u/14936212?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rene Dellefont</b></sub></a><br /><a href="https://github.com/tannerlinsley/react-query/commits?author=babycourageous" title="Code">💻</a> <a href="https://github.com/tannerlinsley/react-query/commits?author=babycourageous" title="Documentation">📖</a></td>
   </tr>
 </table>
 
-<!-- markdownlint-enable -->
+<!-- markdownlint-restore -->
 <!-- prettier-ignore-end -->
 
 <!-- ALL-CONTRIBUTORS-LIST:END -->
diff --git a/docs/src/components/LayoutDocs.js b/docs/src/components/LayoutDocs.js
index cc229a70c..d890d5d94 100644
--- a/docs/src/components/LayoutDocs.js
+++ b/docs/src/components/LayoutDocs.js
@@ -106,7 +106,7 @@ export const LayoutDocs = props => {
                       width: 200,
                     }}
                   >
-                    <div className="sticky top-24 overflow-y-auto">
+                    <div className="sticky top-24">
                       <h4 className="font-semibold uppercase text-sm mb-2 mt-2 text-gray-500">
                         On this page
                       </h4>
diff --git a/docs/src/pages/reference/useMutation.md b/docs/src/pages/reference/useMutation.md
index 1750c9c14..e8b2e9cdc 100644
--- a/docs/src/pages/reference/useMutation.md
+++ b/docs/src/pages/reference/useMutation.md
@@ -23,6 +23,7 @@ const {
   onSettled,
   onSuccess,
   useErrorBoundary,
+  meta,
 })
 
 mutate(variables, {
@@ -71,6 +72,9 @@ mutate(variables, {
   - Set this to `true` if you want mutation errors to be thrown in the render phase and propagate to the nearest error boundary
   - Set this to `false` to disable the behavior of throwing errors to the error boundary.
   - If set to a function, it will be passed the error and should return a boolean indicating whether to show the error in an error boundary (`true`) or return the error as state (`false`)
+- `meta: Record<string, unknown>`
+  - Optional
+  - If set, stores additional information on the mutation cache entry that can be used as needed. It will be accessible wherever the `mutation` is available (eg. `onError`, `onSuccess` functions of the `MutationCache`).
 
 **Returns**
 
diff --git a/src/core/mutation.ts b/src/core/mutation.ts
index 54ba5473a..3846e60ba 100644
--- a/src/core/mutation.ts
+++ b/src/core/mutation.ts
@@ -1,4 +1,4 @@
-import type { MutationOptions, MutationStatus } from './types'
+import type { MutationOptions, MutationStatus, MutationMeta } from './types'
 import type { MutationCache } from './mutationCache'
 import type { MutationObserver } from './mutationObserver'
 import { getLogger } from './logger'
@@ -14,6 +14,7 @@ interface MutationConfig<TData, TError, TVariables, TContext> {
   options: MutationOptions<TData, TError, TVariables, TContext>
   defaultOptions?: MutationOptions<TData, TError, TVariables, TContext>
   state?: MutationState<TData, TError, TVariables, TContext>
+  meta?: MutationMeta
 }
 
 export interface MutationState<
@@ -84,6 +85,7 @@ export class Mutation<
   state: MutationState<TData, TError, TVariables, TContext>
   options: MutationOptions<TData, TError, TVariables, TContext>
   mutationId: number
+  meta: MutationMeta | undefined
 
   private observers: MutationObserver<TData, TError, TVariables, TContext>[]
   private mutationCache: MutationCache
@@ -98,6 +100,7 @@ export class Mutation<
     this.mutationCache = config.mutationCache
     this.observers = []
     this.state = config.state || getDefaultState()
+    this.meta = config.meta
   }
 
   setState(state: MutationState<TData, TError, TVariables, TContext>): void {
diff --git a/src/core/mutationCache.ts b/src/core/mutationCache.ts
index 02b97b19b..eef98e3ec 100644
--- a/src/core/mutationCache.ts
+++ b/src/core/mutationCache.ts
@@ -52,6 +52,7 @@ export class MutationCache extends Subscribable<MutationCacheListener> {
       defaultOptions: options.mutationKey
         ? client.getMutationDefaults(options.mutationKey)
         : undefined,
+      meta: options.meta,
     })
 
     this.add(mutation)
diff --git a/src/core/query.ts b/src/core/query.ts
index 336e9b874..861da15a5 100644
--- a/src/core/query.ts
+++ b/src/core/query.ts
@@ -175,7 +175,6 @@ export class Query<
     this.initialState = config.state || this.getDefaultState(this.options)
     this.state = this.initialState
     this.meta = config.meta
-    this.scheduleGc()
   }
 
   private setOptions(
@@ -214,8 +213,12 @@ export class Query<
   }
 
   private optionalRemove() {
-    if (!this.observers.length && !this.state.isFetching) {
-      this.cache.remove(this)
+    if (!this.observers.length) {
+      if (this.state.isFetching) {
+        this.scheduleGc()
+      } else {
+        this.cache.remove(this)
+      }
     }
   }
 
diff --git a/src/core/queryObserver.ts b/src/core/queryObserver.ts
index f1ab5c8d2..aa36cfd2c 100644
--- a/src/core/queryObserver.ts
+++ b/src/core/queryObserver.ts
@@ -232,24 +232,38 @@ export class QueryObserver<
   }
 
   trackResult(
-    result: QueryObserverResult<TData, TError>
+    result: QueryObserverResult<TData, TError>,
+    defaultedOptions: QueryObserverOptions<
+      TQueryFnData,
+      TError,
+      TData,
+      TQueryData,
+      TQueryKey
+    >
   ): QueryObserverResult<TData, TError> {
     const trackedResult = {} as QueryObserverResult<TData, TError>
 
+    const trackProp = (key: keyof QueryObserverResult) => {
+      if (!this.trackedProps.includes(key)) {
+        this.trackedProps.push(key)
+      }
+    }
+
     Object.keys(result).forEach(key => {
       Object.defineProperty(trackedResult, key, {
         configurable: false,
         enumerable: true,
         get: () => {
-          const typedKey = key as keyof QueryObserverResult
-          if (!this.trackedProps.includes(typedKey)) {
-            this.trackedProps.push(typedKey)
-          }
-          return result[typedKey]
+          trackProp(key as keyof QueryObserverResult)
+          return result[key as keyof QueryObserverResult]
         },
       })
     })
 
+    if (defaultedOptions.useErrorBoundary || defaultedOptions.suspense) {
+      trackProp('error')
+    }
+
     return trackedResult
   }
 
diff --git a/src/core/types.ts b/src/core/types.ts
index 2aa3dda46..583b34a15 100644
--- a/src/core/types.ts
+++ b/src/core/types.ts
@@ -501,6 +501,8 @@ export type MutationKey = string | readonly unknown[]
 
 export type MutationStatus = 'idle' | 'loading' | 'success' | 'error'
 
+export type MutationMeta = Record<string, unknown>
+
 export type MutationFunction<TData = unknown, TVariables = unknown> = (
   variables: TVariables
 ) => Promise<TData>
@@ -536,6 +538,7 @@ export interface MutationOptions<
   retry?: RetryValue<TError>
   retryDelay?: RetryDelayValue<TError>
   _defaulted?: boolean
+  meta?: MutationMeta
 }
 
 export interface MutationObserverOptions<
diff --git a/src/devtools/devtools.tsx b/src/devtools/devtools.tsx
index af098e597..00a226de1 100644
--- a/src/devtools/devtools.tsx
+++ b/src/devtools/devtools.tsx
@@ -55,7 +55,7 @@ interface DevtoolsOptions {
   /**
    * Use this to render the devtools inside a different type of container element for a11y purposes.
    * Any string which corresponds to a valid intrinsic JSX element is allowed.
-   * Defaults to 'footer'.
+   * Defaults to 'aside'.
    */
   containerElement?: string | any
 }
@@ -91,7 +91,7 @@ export function ReactQueryDevtools({
   closeButtonProps = {},
   toggleButtonProps = {},
   position = 'bottom-left',
-  containerElement: Container = 'footer',
+  containerElement: Container = 'aside',
 }: DevtoolsOptions): React.ReactElement | null {
   const rootRef = React.useRef<HTMLDivElement>(null)
   const panelRef = React.useRef<HTMLDivElement>(null)
@@ -221,7 +221,11 @@ export function ReactQueryDevtools({
   if (!isMounted()) return null
 
   return (
-    <Container ref={rootRef} className="ReactQueryDevtools">
+    <Container
+      ref={rootRef}
+      className="ReactQueryDevtools"
+      aria-label="React Query Devtools"
+    >
       <ThemeProvider theme={theme}>
         <ReactQueryDevtoolsPanel
           ref={panelRef as any}
@@ -265,6 +269,9 @@ export function ReactQueryDevtools({
           <Button
             type="button"
             aria-label="Close React Query Devtools"
+            aria-controls="ReactQueryDevtoolsPanel"
+            aria-haspopup="true"
+            aria-expanded="true"
             {...(otherCloseButtonProps as unknown)}
             onClick={e => {
               setIsOpen(false)
@@ -302,6 +309,9 @@ export function ReactQueryDevtools({
           type="button"
           {...otherToggleButtonProps}
           aria-label="Open React Query Devtools"
+          aria-controls="ReactQueryDevtoolsPanel"
+          aria-haspopup="true"
+          aria-expanded="false"
           onClick={e => {
             setIsOpen(true)
             onToggleClick && onToggleClick(e)
@@ -449,7 +459,13 @@ export const ReactQueryDevtoolsPanel = React.forwardRef<
 
   return (
     <ThemeProvider theme={theme}>
-      <Panel ref={ref} className="ReactQueryDevtoolsPanel" {...panelProps}>
+      <Panel
+        ref={ref}
+        className="ReactQueryDevtoolsPanel"
+        aria-label="React Query Devtools Panel"
+        id="ReactQueryDevtoolsPanel"
+        {...panelProps}
+      >
         <style
           dangerouslySetInnerHTML={{
             __html: `
@@ -494,7 +510,7 @@ export const ReactQueryDevtoolsPanel = React.forwardRef<
             maxHeight: '100%',
             overflow: 'auto',
             borderRight: `1px solid ${theme.grayAlt}`,
-            display: 'flex',
+            display: isOpen ? 'flex' : 'none',
             flexDirection: 'column',
           }}
         >
diff --git a/src/react/types.ts b/src/react/types.ts
index fce406915..79aa3d450 100644
--- a/src/react/types.ts
+++ b/src/react/types.ts
@@ -9,6 +9,7 @@ import {
   QueryKey,
   MutationFunction,
   MutateOptions,
+  MutationMeta,
 } from '../core/types'
 
 export interface UseBaseQueryOptions<
@@ -97,6 +98,7 @@ export interface UseMutationOptions<
   retry?: RetryValue<TError>
   retryDelay?: RetryDelayValue<TError>
   useErrorBoundary?: boolean | ((error: TError) => boolean)
+  meta?: MutationMeta
 }
 
 export type UseMutateFunction<
diff --git a/src/react/useBaseQuery.ts b/src/react/useBaseQuery.ts
index 693944fc3..3adb64e05 100644
--- a/src/react/useBaseQuery.ts
+++ b/src/react/useBaseQuery.ts
@@ -144,7 +144,7 @@ export function useBaseQuery<
 
   // Handle result property usage tracking
   if (defaultedOptions.notifyOnChangeProps === 'tracked') {
-    result = observer.trackResult(result)
+    result = observer.trackResult(result, defaultedOptions)
   }
 
   return result


Test patch:
diff --git a/src/core/tests/query.test.tsx b/src/core/tests/query.test.tsx
index 583799cfb..efdc4894e 100644
--- a/src/core/tests/query.test.tsx
+++ b/src/core/tests/query.test.tsx
@@ -544,6 +544,29 @@ describe('query', () => {
     expect(queryCache.find(key)).toBeUndefined()
   })
 
+  test('should be garbage collected later when unsubscribed and query is fetching', async () => {
+    const key = queryKey()
+    const observer = new QueryObserver(queryClient, {
+      queryKey: key,
+      queryFn: async () => {
+        await sleep(20)
+        return 'data'
+      },
+      cacheTime: 10,
+    })
+    const unsubscribe = observer.subscribe()
+    await sleep(20)
+    expect(queryCache.find(key)).toBeDefined()
+    observer.refetch()
+    unsubscribe()
+    await sleep(10)
+    // unsubscribe should not remove even though cacheTime has elapsed b/c query is still fetching
+    expect(queryCache.find(key)).toBeDefined()
+    await sleep(10)
+    // should be removed after an additional staleTime wait
+    expect(queryCache.find(key)).toBeUndefined()
+  })
+
   test('should not be garbage collected unless there are no subscribers', async () => {
     const key = queryKey()
     const observer = new QueryObserver(queryClient, {
diff --git a/src/react/tests/QueryResetErrorBoundary.test.tsx b/src/react/tests/QueryResetErrorBoundary.test.tsx
index f73210d25..5fc1e366f 100644
--- a/src/react/tests/QueryResetErrorBoundary.test.tsx
+++ b/src/react/tests/QueryResetErrorBoundary.test.tsx
@@ -592,4 +592,64 @@ describe('QueryErrorResetBoundary', () => {
 
     expect(rendered.queryByText('page')).not.toBeNull()
   })
+
+  it('should show error boundary when using tracked queries even though we do not track the error field', 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,
+          notifyOnChangeProps: 'tracked',
+        }
+      )
+      return <div>{data}</div>
+    }
+
+    const rendered = renderWithClient(
+      queryClient,
+      <QueryErrorResetBoundary>
+        {({ reset }) => (
+          <ErrorBoundary
+            onReset={reset}
+            fallbackRender={({ resetErrorBoundary }) => (
+              <div>
+                <div>error boundary</div>
+                <button
+                  onClick={() => {
+                    resetErrorBoundary()
+                  }}
+                >
+                  retry
+                </button>
+              </div>
+            )}
+          >
+            <Page />
+          </ErrorBoundary>
+        )}
+      </QueryErrorResetBoundary>
+    )
+
+    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/useMutation.test.tsx b/src/react/tests/useMutation.test.tsx
index b253fd235..f4c25e099 100644
--- a/src/react/tests/useMutation.test.tsx
+++ b/src/react/tests/useMutation.test.tsx
@@ -519,4 +519,68 @@ describe('useMutation', () => {
 
     consoleMock.mockRestore()
   })
+
+  it('should pass meta to mutation', async () => {
+    const consoleMock = mockConsoleError()
+
+    const errorMock = jest.fn()
+    const successMock = jest.fn()
+
+    const queryClientMutationMeta = new QueryClient({
+      mutationCache: new MutationCache({
+        onSuccess: (_, __, ___, mutation) => {
+          successMock(mutation.meta?.metaSuccessMessage)
+        },
+        onError: (_, __, ___, mutation) => {
+          errorMock(mutation.meta?.metaErrorMessage)
+        },
+      }),
+    })
+
+    const metaSuccessMessage = 'mutation succeeded'
+    const metaErrorMessage = 'mutation failed'
+
+    function Page() {
+      const { mutate: succeed, isSuccess } = useMutation(async () => '', {
+        meta: { metaSuccessMessage },
+      })
+      const { mutate: error, isError } = useMutation(
+        async () => {
+          throw new Error('')
+        },
+        {
+          meta: { metaErrorMessage },
+        }
+      )
+
+      return (
+        <div>
+          <button onClick={() => succeed()}>succeed</button>
+          <button onClick={() => error()}>error</button>
+          {isSuccess && <div>successTest</div>}
+          {isError && <div>errorTest</div>}
+        </div>
+      )
+    }
+
+    const { getByText, queryByText } = renderWithClient(
+      queryClientMutationMeta,
+      <Page />
+    )
+
+    fireEvent.click(getByText('succeed'))
+    fireEvent.click(getByText('error'))
+
+    await waitFor(() => {
+      expect(queryByText('successTest')).not.toBeNull()
+      expect(queryByText('errorTest')).not.toBeNull()
+    })
+
+    expect(successMock).toHaveBeenCalledTimes(1)
+    expect(successMock).toHaveBeenCalledWith(metaSuccessMessage)
+    expect(errorMock).toHaveBeenCalledTimes(1)
+    expect(errorMock).toHaveBeenCalledWith(metaErrorMessage)
+
+    consoleMock.mockRestore()
+  })
 })