Repository: tannerlinsley/react-query
Pull request #1124 (2020-10-01 20:50:22)

fix: refetching on infinite query when a page was deleted
Issue: #1115 

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



Patch:
diff --git a/docs/src/pages/docs/quick-start.md b/docs/src/pages/docs/quick-start.md
index 7a29d3015..a02da8c08 100644
--- a/docs/src/pages/docs/quick-start.md
+++ b/docs/src/pages/docs/quick-start.md
@@ -49,8 +49,8 @@ function Todos() {
       <button
         onClick={() =>
           addTodo({
-            id: Date.now()
-            title: 'Do Laundry',
+            id: Date.now(),
+            title: 'Do Laundry'
           })
         }
       >
diff --git a/docs/src/pages/index.js b/docs/src/pages/index.js
index 9fddba2e2..ab80037c0 100644
--- a/docs/src/pages/index.js
+++ b/docs/src/pages/index.js
@@ -15,6 +15,7 @@ const supporters = [
   ['Gabe Ragland', 'https://github.com/gragland'],
   ['Matt Wood', 'https://github.com/mattwoodnyc'],
   ['Joshua Turner', 'https://github.com/joshua-turner'],
+  ['Fillip Peyton', 'https://github.com/fillippeyton'],
 ]
 
 const fans = [
@@ -32,6 +33,7 @@ const fans = [
   'Juliano Farias (@frontendwizard)',
   'Kristóf Poduszló (@kripod)',
   'Peter Pistorius (@peterp)',
+  'Agustín Villalobos (@agustin-v)',
 ]
 
 const Home = () => {
diff --git a/src/core/query.ts b/src/core/query.ts
index 3b461cf7d..adf8a2bc2 100644
--- a/src/core/query.ts
+++ b/src/core/query.ts
@@ -468,6 +468,10 @@ export class Query<TResult, TError> {
         cursor = config.getFetchMore(lastPage, pages)
       }
 
+      if (!Boolean(cursor) && typeof lastPage !== 'undefined') {
+        return pages
+      }
+
       const page = await config.queryFn(...params, cursor)
 
       return prepend ? [page, ...pages] : [...pages, page]


Test patch:
diff --git a/src/react/tests/useInfiniteQuery.test.tsx b/src/react/tests/useInfiniteQuery.test.tsx
index 9af69372b..9ebbbc91a 100644
--- a/src/react/tests/useInfiniteQuery.test.tsx
+++ b/src/react/tests/useInfiniteQuery.test.tsx
@@ -20,11 +20,11 @@ const initialItems = (page: number): Result => {
   }
 }
 
-const fetchItems = async (page: number, ts: number): Promise<Result> => {
+const fetchItems = async (page: number, ts: number, nextId?: any): Promise<Result> => {
   await sleep(10)
   return {
     items: [...new Array(10)].fill(null).map((_, d) => page * pageSize + d),
-    nextId: page + 1,
+    nextId: nextId ?? page + 1,
     ts,
   }
 }
@@ -1063,4 +1063,137 @@ describe('useInfiniteQuery', () => {
     // ensure that Item: 4 is no longer rendered
     expect(rendered.queryAllByText('Item: 4')).toHaveLength(0)
   })
+
+  it('should compute canFetchMore correctly for falsy getFetchMore return value on refetching', async () => {
+    const key = queryKey()
+    const MAX = 2
+  
+    function Page() {
+      const fetchCountRef = React.useRef(0)
+      const [isRemovedLastPage, setIsRemovedLastPage] = React.useState<boolean>(
+        false
+      )
+      const {
+        status,
+        data,
+        error,
+        isFetching,
+        isFetchingMore,
+        fetchMore,
+        canFetchMore,
+        refetch,
+      } = useInfiniteQuery<Result, Error, [string, number]>(
+        key,
+        (_key, nextId = 0) =>
+          fetchItems(
+            nextId,
+            fetchCountRef.current++,
+            nextId === MAX || (nextId === MAX - 1 && isRemovedLastPage)
+              ? false
+              : undefined
+          ),
+        {
+          getFetchMore: (lastGroup, _allGroups) => lastGroup.nextId,
+        }
+      )
+  
+      return (
+        <div>
+          <h1>Pagination</h1>
+          {status === 'loading' ? (
+            'Loading...'
+          ) : status === 'error' ? (
+            <span>Error: {error?.message}</span>
+          ) : (
+            <>
+              <div>Data:</div>
+              {data?.map((page, i) => (
+                <div key={i}>
+                  <div>
+                    Page {i}: {page.ts}
+                  </div>
+                  <div key={i}>
+                    {page.items.map(item => (
+                      <p key={item}>Item: {item}</p>
+                    ))}
+                  </div>
+                </div>
+              ))}
+              <div>
+                <button
+                  onClick={() => fetchMore()}
+                  disabled={!canFetchMore || Boolean(isFetchingMore)}
+                >
+                  {isFetchingMore
+                    ? 'Loading more...'
+                    : canFetchMore
+                    ? 'Load More'
+                    : 'Nothing more to load'}
+                </button>
+                <button onClick={() => refetch()}>Refetch</button>
+                <button onClick={() => setIsRemovedLastPage(true)}>
+                  Remove Last Page
+                </button>
+              </div>
+              <div>
+                {isFetching && !isFetchingMore
+                  ? 'Background Updating...'
+                  : null}
+              </div>
+            </>
+          )}
+        </div>
+      )
+    }
+  
+    const rendered = render(<Page />)
+  
+    rendered.getByText('Loading...')
+  
+    await waitFor(() => {
+      rendered.getByText('Item: 9')
+      rendered.getByText('Page 0: 0')
+    })
+  
+    fireEvent.click(rendered.getByText('Load More'))
+  
+    await waitFor(() => rendered.getByText('Loading more...'))
+  
+    await waitFor(() => {
+      rendered.getByText('Item: 19')
+      rendered.getByText('Page 0: 0')
+      rendered.getByText('Page 1: 1')
+    })
+  
+    fireEvent.click(rendered.getByText('Load More'))
+  
+    await waitFor(() => rendered.getByText('Loading more...'))
+  
+    await waitFor(() => {
+      rendered.getByText('Item: 29')
+      rendered.getByText('Page 0: 0')
+      rendered.getByText('Page 1: 1')
+      rendered.getByText('Page 2: 2')
+    })
+  
+    rendered.getByText('Nothing more to load')
+  
+    fireEvent.click(rendered.getByText('Remove Last Page'))
+
+    await waitForMs(10)
+
+    fireEvent.click(rendered.getByText('Refetch'))
+
+    await waitFor(() => rendered.getByText('Background Updating...'))
+  
+    await waitFor(() => {
+      rendered.getByText('Page 0: 3')
+      rendered.getByText('Page 1: 4')
+    })
+  
+    expect(rendered.queryByText('Item: 29')).toBeNull()
+    expect(rendered.queryByText('Page 2: 5')).toBeNull()
+  
+    rendered.getByText('Nothing more to load')
+  })
 })