Repository: tannerlinsley/react-query (ts)

Issue:
Infinite re-renders when using `select` in `useInfiniteQuery`
**Describe the bug**
When I transform data from infinite query with `select` option, I get infinite re-renders in my component

**To Reproduce**
Add following component and render it (implied that it'll be wrapped in `QueryClientProvider` of course):
```js
import { useInfiniteQuery } from 'react-query';
import { nanoid } from 'nanoid';

const App = () => {
  const query = useInfiniteQuery(
    'todos',
    ({ pageParam = 0 }) => fetchTodo(pageParam),
    {
      getNextPageParam: ({ nextCursor }) => nextCursor,
      select: ({ pages, pageParams }) => ({
        pages: pages.map(({ todos }) => (
          todos.map(todo => ({
            ...todo,
            localId: nanoid(),
          }))
        )),
        pageParams: [...pageParams],
      })
    },
  );

  console.log('re-render')

  return null;
};

const todos = [];

for (let i = 0; i < 100; i++) {
  todos.push({ id: nanoid(), text: `Todo ${i + 1}` });
}

const TODOS_FETCH_PORTION = 10;

const fetchTodo = (cursor) => {
  const nextCursor = cursor + TODOS_FETCH_PORTION;
  const result = {
    todos: todos.slice(cursor, nextCursor),
  };
  if (todos.length - 1 >= nextCursor) {
    result.nextCursor = nextCursor;
  }
  return result;
};

export default App;
```

**Expected behavior**
Expected `App` to not re-render infinitely.

**Desktop (please complete the following information):**
 - OS: macOS 10.13.6
 - Browser: Chrome 96.04

**Additional info**
With `useQuery` this bug does not occur

Pull request:
fix(useInfiniteQuery): fix infinite re-renders when select produced a new result
by correctly forwarding notifyOptions

closes #3068 

Patch:
diff --git a/src/core/infiniteQueryObserver.ts b/src/core/infiniteQueryObserver.ts
index 19ef26465..0077a5752 100644
--- a/src/core/infiniteQueryObserver.ts
+++ b/src/core/infiniteQueryObserver.ts
@@ -6,7 +6,11 @@ import type {
   InfiniteQueryObserverResult,
 } from './types'
 import type { QueryClient } from './queryClient'
-import { ObserverFetchOptions, QueryObserver } from './queryObserver'
+import {
+  NotifyOptions,
+  ObserverFetchOptions,
+  QueryObserver,
+} from './queryObserver'
 import {
   hasNextPage,
   hasPreviousPage,
@@ -67,12 +71,16 @@ export class InfiniteQueryObserver<
       TError,
       TData,
       TQueryData
-    >
+    >,
+    notifyOptions?: NotifyOptions
   ): void {
-    super.setOptions({
-      ...options,
-      behavior: infiniteQueryBehavior(),
-    })
+    super.setOptions(
+      {
+        ...options,
+        behavior: infiniteQueryBehavior(),
+      },
+      notifyOptions
+    )
   }
 
   getOptimisticResult(


Test patch:
diff --git a/src/react/tests/suspense.test.tsx b/src/react/tests/suspense.test.tsx
index 50a63e22d..06c1d6dd3 100644
--- a/src/react/tests/suspense.test.tsx
+++ b/src/react/tests/suspense.test.tsx
@@ -103,8 +103,8 @@ describe("useQuery's in Suspense mode", () => {
     fireEvent.click(rendered.getByText('next'))
     await sleep(10)
 
-    expect(states.length).toBe(3)
-    expect(states[2]).toMatchObject({
+    expect(states.length).toBe(2)
+    expect(states[1]).toMatchObject({
       data: { pages: [2], pageParams: [undefined] },
       status: 'success',
     })
diff --git a/src/react/tests/useInfiniteQuery.test.tsx b/src/react/tests/useInfiniteQuery.test.tsx
index 540b44956..453142d29 100644
--- a/src/react/tests/useInfiniteQuery.test.tsx
+++ b/src/react/tests/useInfiniteQuery.test.tsx
@@ -15,6 +15,7 @@ import {
   QueryClient,
   QueryCache,
   QueryFunctionContext,
+  InfiniteData,
 } from '../..'
 
 interface Result {
@@ -293,6 +294,41 @@ describe('useInfiniteQuery', () => {
     })
   })
 
+  it('should be able to select a new result and not cause infinite renders', async () => {
+    const key = queryKey()
+    const states: UseInfiniteQueryResult<{ count: number; id: number }>[] = []
+    let selectCalled = 0
+
+    function Page() {
+      const state = useInfiniteQuery(key, () => ({ count: 1 }), {
+        select: React.useCallback((data: InfiniteData<{ count: number }>) => {
+          selectCalled++
+          return {
+            pages: data.pages.map(x => ({ ...x, id: Math.random() })),
+            pageParams: data.pageParams,
+          }
+        }, []),
+      })
+      states.push(state)
+      return null
+    }
+
+    renderWithClient(queryClient, <Page />)
+
+    await sleep(20)
+
+    expect(states.length).toBe(2)
+    expect(selectCalled).toBe(1)
+    expect(states[0]).toMatchObject({
+      data: undefined,
+      isSuccess: false,
+    })
+    expect(states[1]).toMatchObject({
+      data: { pages: [{ count: 1 }] },
+      isSuccess: true,
+    })
+  })
+
   it('should be able to reverse the data', async () => {
     const key = queryKey()
     const states: UseInfiniteQueryResult<number>[] = []