Repository: tannerlinsley/react-query (ts)

Issue:
useMutation - cancelQueries triggers active useQuery null error 
**Describe the bug**
It seems that `cancelQueries` inside a `useMutation` somehow triggers a `null` error in the associated `useQuery` IF that query is actively fetching (i.e. after being updated). So if you run a mutation and the run it again before the query fully resolves.

The sandboxes below demonstrate that 
https://codesandbox.io/s/rq-optupd-quick-forked-6zlmp is a stripped down mutation that only cancels the associated query. 
https://codesandbox.io/s/rq-optupd-quick-rbt3k is a full `useMutation` implementation forked from the docs.

In either case - commenting out `cancelQueries` seems to prevent the buggy behavior.

**To Reproduce**
Go to either sandbox
Add a todo and WHILE the todo query is updating in background (you know this by the display text) add another todo. You will see in the console that an error from the todo query fires.

The error is `null` and doesn't seem to impact the mutation but if you have error toasts or any other query error handling that would all run obviously.

**Expected behavior**
if a mutation fires while a query is updating in the background the `cancelQueries` should not force a `null` error on that query.

**Desktop (please complete the following information):**
 - OS: Mac OS 10.14
 - Browser: chrome
 - Version: 88

**Additional context**
This behavior happens in the CodeSandbox environment above but I have experienced it outside of a sandbox as well in React Query v 3.5.16 as well as after updating to the current version of React Query. The one strange thing is that commenting out either `cancelQueries` or `invalidateQueries` in my app seems to prevent the behavior - however commenting out `invalidateQueries` in the sandbox had no effect.

**Current Workaround**
To suppress any `useQuery` `onError` code from running I am currently using the following workaround: 

```js
{
  onError: (error) => {
    if (!error) return // exit early if there is no error (i.e. the error is null as is the case in the cancelQueries behavior)
    /* ALL ERROR HANDLING HERE *.
  }
}
```

Pull request:
fix(useQuery): don't inform observers about CancelledErrors
the added test case confirms the issue - it fails on master.

closes #1876 

Patch:
diff --git a/src/core/queryObserver.ts b/src/core/queryObserver.ts
index cc1cd3e85..e1295cbd3 100644
--- a/src/core/queryObserver.ts
+++ b/src/core/queryObserver.ts
@@ -22,6 +22,7 @@ import type { QueryClient } from './queryClient'
 import { focusManager } from './focusManager'
 import { Subscribable } from './subscribable'
 import { getLogger } from './logger'
+import { isCancelledError } from './retryer'
 
 type QueryObserverListener<TData, TError> = (
   result: QueryObserverResult<TData, TError>
@@ -660,7 +661,7 @@ export class QueryObserver<
 
     if (action.type === 'success') {
       notifyOptions.onSuccess = true
-    } else if (action.type === 'error') {
+    } else if (action.type === 'error' && !isCancelledError(action.error)) {
       notifyOptions.onError = true
     }
 


Test patch:
diff --git a/src/react/tests/useQuery.test.tsx b/src/react/tests/useQuery.test.tsx
index 7e94f0209..ae35708ca 100644
--- a/src/react/tests/useQuery.test.tsx
+++ b/src/react/tests/useQuery.test.tsx
@@ -473,6 +473,33 @@ describe('useQuery', () => {
     consoleMock.mockRestore()
   })
 
+  it('should not call onError when receiving a CancelledError', async () => {
+    const key = queryKey()
+    const onError = jest.fn()
+    const consoleMock = mockConsoleError()
+
+    function Page() {
+      useQuery<unknown>(
+        key,
+        async () => {
+          await sleep(10)
+          return 23
+        },
+        {
+          onError,
+        }
+      )
+      return null
+    }
+
+    renderWithClient(queryClient, <Page />)
+
+    await sleep(5)
+    await queryClient.cancelQueries(key)
+    expect(onError).not.toHaveBeenCalled()
+    consoleMock.mockRestore()
+  })
+
   it('should call onSettled after a query has been fetched', async () => {
     const key = queryKey()
     const states: UseQueryResult<string>[] = []