Repository: tannerlinsley/react-query
Pull request #1054 (2020-09-18 14:03:06)

fix: query should try and throw again after error boundary reset
Fixes https://github.com/tannerlinsley/react-query/issues/1053


Patch:
diff --git a/src/react/useBaseQuery.ts b/src/react/useBaseQuery.ts
index 7aebcc9e0..fe217ca8f 100644
--- a/src/react/useBaseQuery.ts
+++ b/src/react/useBaseQuery.ts
@@ -33,15 +33,14 @@ export function useBaseQuery<TResult, TError>(
   observerRef.current = observer
 
   // Subscribe to the observer
-  React.useEffect(
-    () =>
-      observer.subscribe(() => {
-        if (isMounted()) {
-          rerender()
-        }
-      }),
-    [isMounted, observer, rerender]
-  )
+  React.useEffect(() => {
+    errorResetBoundary.clearReset()
+    return observer.subscribe(() => {
+      if (isMounted()) {
+        rerender()
+      }
+    })
+  }, [isMounted, observer, rerender, errorResetBoundary])
 
   // Update config
   if (!firstRender) {


Test patch:
diff --git a/src/core/tests/queryCache.test.tsx b/src/core/tests/queryCache.test.tsx
index ab24aaa79..76de2d128 100644
--- a/src/core/tests/queryCache.test.tsx
+++ b/src/core/tests/queryCache.test.tsx
@@ -314,6 +314,28 @@ describe('queryCache', () => {
     expect(queryFn2).toHaveBeenCalledTimes(2)
   })
 
+  test('invalidateQueries should not refetch inactive queries by default', async () => {
+    const key1 = queryKey()
+    const key2 = queryKey()
+    const queryFn1 = jest.fn()
+    const queryFn2 = jest.fn()
+    const cache = new QueryCache()
+    await cache.fetchQuery(key1, queryFn1)
+    await cache.fetchQuery(key2, queryFn2)
+    const query1 = cache.getQuery(key1)!
+    const observer = new QueryObserver({
+      ...query1.config,
+      enabled: false,
+      staleTime: Infinity,
+    })
+    observer.subscribe()
+    await cache.invalidateQueries(key1)
+    observer.unsubscribe()
+    cache.clear()
+    expect(queryFn1).toHaveBeenCalledTimes(1)
+    expect(queryFn2).toHaveBeenCalledTimes(1)
+  })
+
   test('getQueries should return queries that partially match queryKey', async () => {
     const key1 = queryKey()
     const key2 = queryKey()
diff --git a/src/react/tests/ReactQueryResetErrorBoundary.test.tsx b/src/react/tests/ReactQueryResetErrorBoundary.test.tsx
index c70891448..fb8f65ab8 100644
--- a/src/react/tests/ReactQueryResetErrorBoundary.test.tsx
+++ b/src/react/tests/ReactQueryResetErrorBoundary.test.tsx
@@ -63,4 +63,61 @@ describe('ReactQueryResetErrorBoundary', () => {
 
     consoleMock.mockRestore()
   })
+
+  it('should throw again on error after the reset error boundary has been reset', async () => {
+    const key = queryKey()
+    const consoleMock = mockConsoleError()
+    let fetchCount = 0
+
+    function Page() {
+      const { data } = useQuery(
+        key,
+        async () => {
+          fetchCount++
+          await sleep(10)
+          throw new Error('Error')
+        },
+        {
+          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'))
+    fireEvent.click(rendered.getByText('retry'))
+    await waitFor(() => rendered.getByText('error boundary'))
+    await waitFor(() => rendered.getByText('retry'))
+    fireEvent.click(rendered.getByText('retry'))
+    await waitFor(() => rendered.getByText('error boundary'))
+    expect(fetchCount).toBe(3)
+
+    consoleMock.mockRestore()
+  })
 })
diff --git a/src/react/tests/useQuery.test.tsx b/src/react/tests/useQuery.test.tsx
index a92546995..2083bdf71 100644
--- a/src/react/tests/useQuery.test.tsx
+++ b/src/react/tests/useQuery.test.tsx
@@ -584,6 +584,46 @@ describe('useQuery', () => {
     })
   })
 
+  it('should not refetch disabled query when invalidated with invalidateQueries', async () => {
+    const key = queryKey()
+    const states: QueryResult<number>[] = []
+    let count = 0
+
+    function Page() {
+      const state = useQuery(
+        key,
+        async () => {
+          await sleep(10)
+          count++
+          return count
+        },
+        { enabled: false }
+      )
+
+      states.push(state)
+
+      React.useEffect(() => {
+        setTimeout(() => {
+          queryCache.invalidateQueries(key)
+        }, 20)
+      }, [])
+
+      return null
+    }
+
+    render(<Page />)
+
+    await waitForMs(100)
+
+    expect(states.length).toBe(1)
+    expect(states[0]).toMatchObject({
+      data: undefined,
+      isFetching: false,
+      isSuccess: false,
+      isStale: true,
+    })
+  })
+
   it('should keep the previous data when keepPreviousData is set', async () => {
     const key = queryKey()
     const states: QueryResult<number>[] = []