Repository: tannerlinsley/react-query (ts)

Issue:
Infinate querying when query fails and using suspense + enabled flag
**Describe the bug**
Using suspense + enabled flag to delay querying, when a query fails will cause infinite querying.

**To Reproduce**

Core code:
```ts
function query() {
  return new Promise((resolve, reject) => {
    console.count("query");
    setTimeout(() => reject("Query Error"), 1000);
  });
}

function DataFetch() {
  const [enabled, setEnabledStatus] = useState(false);
  useQuery("mock", query, { suspense: true, enabled, retry: 0 });

  return <button onClick={() => setEnabledStatus(true)}>Start</button>;
}
```

Complete code: https://codesandbox.io/s/react-query-bug-suspense-enabled-flag-2w762?file=/src/App.tsx

Steps to reproduce the behavior:
1. Click the `Start` Button
2. Look at `Console`, you will see many queries.

**Expected behavior**
Throw Error and only query once.

**Screenshots**
![image](https://user-images.githubusercontent.com/21964227/124413644-a4c8a800-dd83-11eb-8159-6d27b8a2a7dd.png)

**Desktop (please complete the following information):**
 - OS: macOS
 - Browser: chrome
 - Version: 91.0.4472.114

**Additional context**
After removing the `enabled` or `suspense` flag, anything works well.


Pull request:
fix(core): remove enabled condition in queryObserver
closes #2434

(would a description be enough as an issue link?)

Patch:
diff --git a/src/core/queryObserver.ts b/src/core/queryObserver.ts
index aa36cfd2c..0e412f632 100644
--- a/src/core/queryObserver.ts
+++ b/src/core/queryObserver.ts
@@ -790,9 +790,7 @@ function shouldFetchOptionally(
   return (
     options.enabled !== false &&
     (query !== prevQuery || prevOptions.enabled === false) &&
-    (!options.suspense ||
-      query.state.status !== 'error' ||
-      prevOptions.enabled === false) &&
+    (!options.suspense || query.state.status !== 'error') &&
     isStale(query, options)
   )
 }


Test patch:
diff --git a/src/react/tests/suspense.test.tsx b/src/react/tests/suspense.test.tsx
index 06c1d6dd3..8ed6d58bb 100644
--- a/src/react/tests/suspense.test.tsx
+++ b/src/react/tests/suspense.test.tsx
@@ -891,6 +891,76 @@ describe("useQuery's in Suspense mode", () => {
     consoleMock.mockRestore()
   })
 
+  it('should error catched in error boundary without infinite loop when enabled changed', async () => {
+    const consoleMock = mockConsoleError()
+
+    const succeed = false
+
+    function Page() {
+      const queryKeys = '1'
+      const [enabled, setEnabled] = React.useState(false)
+
+      const result = useQuery(
+        [queryKeys],
+        async () => {
+          await sleep(10)
+          if (!succeed) {
+            throw new Error('Suspense Error Bingo')
+          } else {
+            return 'data'
+          }
+        },
+        {
+          retry: false,
+          suspense: true,
+          enabled,
+        }
+      )
+      return (
+        <div>
+          <span>rendered</span> <span>{result.data}</span>
+          <button
+            aria-label="fail"
+            onClick={() => {
+              setEnabled(true)
+            }}
+          >
+            fail
+          </button>
+        </div>
+      )
+    }
+
+    function App() {
+      const { reset } = useQueryErrorResetBoundary()
+      return (
+        <ErrorBoundary
+          onReset={reset}
+          fallbackRender={() => <div>error boundary</div>}
+        >
+          <React.Suspense fallback="Loading...">
+            <Page />
+          </React.Suspense>
+        </ErrorBoundary>
+      )
+    }
+
+    const rendered = renderWithClient(queryClient, <App />)
+
+    // render empty data with 'rendered' when enabled is false
+    await waitFor(() => rendered.getByText('rendered'))
+
+    // change enabled to true
+    fireEvent.click(rendered.getByLabelText('fail'))
+
+    // render pending fallback
+    await waitFor(() => rendered.getByText('Loading...'))
+
+    // render error boundary fallback (error boundary)
+    await waitFor(() => rendered.getByText('error boundary'))
+    consoleMock.mockRestore()
+  })
+
   it('should render the correct amount of times in Suspense mode when cacheTime is set to 0', async () => {
     const key = queryKey()
     let state: UseQueryResult<number> | null = null