Repository: tannerlinsley/react-query
Pull request #1004 (2020-09-12 18:55:22)

fix: useInfiniteQuery fetchMore should not throw

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



Patch:
diff --git a/src/core/query.ts b/src/core/query.ts
index 96f66cdb9..a95ddd912 100644
--- a/src/core/query.ts
+++ b/src/core/query.ts
@@ -296,7 +296,7 @@ export class Query<TResult, TError> {
     }
   }
 
-  async fetchMore(
+  fetchMore(
     fetchMoreVariable?: unknown,
     options?: FetchMoreOptions,
     config?: ResolvedQueryConfig<TResult, TError>
@@ -380,7 +380,7 @@ export class Query<TResult, TError> {
     return this.promise
   }
 
-  private async startFetch(
+  private startFetch(
     config: ResolvedQueryConfig<TResult, TError>,
     params: unknown[],
     _options?: FetchOptions
@@ -397,7 +397,7 @@ export class Query<TResult, TError> {
     return this.tryFetchData(config, fetchData)
   }
 
-  private async startInfiniteFetch(
+  private startInfiniteFetch(
     config: ResolvedQueryConfig<TResult, TError>,
     params: unknown[],
     options?: FetchOptions
@@ -455,7 +455,7 @@ export class Query<TResult, TError> {
     return this.tryFetchData(config, fetchData)
   }
 
-  private async tryFetchData<T>(
+  private tryFetchData<T>(
     config: ResolvedQueryConfig<TResult, TError>,
     fn: QueryFunction<T>
   ): Promise<T> {
diff --git a/src/core/queryObserver.ts b/src/core/queryObserver.ts
index 13c1f109d..d3a062bdb 100644
--- a/src/core/queryObserver.ts
+++ b/src/core/queryObserver.ts
@@ -1,8 +1,9 @@
 import {
   getStatusProps,
-  isServer,
   isDocumentVisible,
+  isServer,
   isValidTimeout,
+  noop,
 } from './utils'
 import type { QueryResult, ResolvedQueryConfig } from './types'
 import type { Query, Action, FetchMoreOptions, RefetchOptions } from './query'
@@ -108,23 +109,21 @@ export class QueryObserver<TResult, TError> {
     return this.currentQuery.clear()
   }
 
-  async refetch(options?: RefetchOptions): Promise<TResult | undefined> {
+  refetch(options?: RefetchOptions): Promise<TResult | undefined> {
     return this.currentQuery.refetch(options, this.config)
   }
 
-  async fetchMore(
+  fetchMore(
     fetchMoreVariable?: unknown,
     options?: FetchMoreOptions
   ): Promise<TResult | undefined> {
-    return this.currentQuery.fetchMore(fetchMoreVariable, options, this.config)
+    return this.currentQuery
+      .fetchMore(fetchMoreVariable, options, this.config)
+      .catch(noop)
   }
 
-  async fetch(): Promise<TResult | undefined> {
-    try {
-      return await this.currentQuery.fetch(undefined, this.config)
-    } catch {
-      // ignore
-    }
+  fetch(): Promise<TResult | undefined> {
+    return this.currentQuery.fetch(undefined, this.config).catch(noop)
   }
 
   private optionalFetch(): void {
diff --git a/src/core/utils.ts b/src/core/utils.ts
index c10e53631..7875894a1 100644
--- a/src/core/utils.ts
+++ b/src/core/utils.ts
@@ -36,8 +36,8 @@ export function uid(): number {
 
 export const isServer = typeof window === 'undefined'
 
-export function noop(): void {
-  return void 0
+export function noop(): undefined {
+  return undefined
 }
 
 export let Console: ConsoleObject = console || {
diff --git a/src/react/useBaseQuery.ts b/src/react/useBaseQuery.ts
index cce86a709..b81182481 100644
--- a/src/react/useBaseQuery.ts
+++ b/src/react/useBaseQuery.ts
@@ -5,7 +5,7 @@ import { getResolvedQueryConfig } from '../core/config'
 import { QueryObserver } from '../core/queryObserver'
 import { QueryResultBase, QueryKey, QueryConfig } from '../core/types'
 import { useErrorResetBoundary } from './ReactQueryErrorResetBoundary'
-import { useQueryCache } from '.'
+import { useQueryCache } from './ReactQueryCacheProvider'
 import { useContextConfig } from './ReactQueryConfigProvider'
 
 export function useBaseQuery<TResult, TError>(


Test patch:
diff --git a/src/react/tests/useInfiniteQuery.test.tsx b/src/react/tests/useInfiniteQuery.test.tsx
index e5155c101..a85f536d7 100644
--- a/src/react/tests/useInfiniteQuery.test.tsx
+++ b/src/react/tests/useInfiniteQuery.test.tsx
@@ -1,7 +1,7 @@
 import { render, waitFor, fireEvent } from '@testing-library/react'
 import * as React from 'react'
 
-import { sleep, queryKey, waitForMs } from './utils'
+import { sleep, queryKey, waitForMs, mockConsoleError } from './utils'
 import { useInfiniteQuery, useQueryCache } from '..'
 import { InfiniteQueryResult } from '../../core'
 
@@ -112,6 +112,48 @@ describe('useInfiniteQuery', () => {
     })
   })
 
+  it('should not throw when fetchMore returns an error', async () => {
+    const consoleMock = mockConsoleError()
+    const key = queryKey()
+    let noThrow: boolean
+
+    function Page() {
+      const start = 1
+      const state = useInfiniteQuery(
+        key,
+        async (_key, page: number = start) => {
+          if (page === 2) {
+            throw new Error('error')
+          }
+          return page
+        },
+        {
+          retry: 1,
+          retryDelay: 10,
+          getFetchMore: (lastPage, _pages) => lastPage + 1,
+        }
+      )
+
+      const { fetchMore } = state
+
+      React.useEffect(() => {
+        setTimeout(async () => {
+          try {
+            await fetchMore()
+            noThrow = true
+          } catch (error) {}
+        }, 20)
+      }, [fetchMore])
+
+      return null
+    }
+
+    render(<Page />)
+
+    await waitFor(() => expect(noThrow).toBe(true))
+    consoleMock.mockRestore()
+  })
+
   it('should keep the previous data when keepPreviousData is set', async () => {
     const key = queryKey()
     const states: InfiniteQueryResult<string>[] = []