Repository: tannerlinsley/react-query (ts)

Issue:
Data becomes unstable when select is inlined
### Describe the bug

react-query's structured sharing becomes unstable when selecting data using inline `select`.

### Your minimal, reproducible example

https://codesandbox.io/s/keen-dawn-jilgs?file=/src/index.js

### Steps to reproduce

1. Click `rerender!`


### Expected behavior

When the `rerender!` button is clicked, it should not print `data changed!`

### How often does this bug happen?

Every time

### Screenshots or Videos

_No response_

### Platform

- Chrome

### react-query version

v3.34.7 and above

### TypeScript version

_No response_

### Additional context

May be caused by [#3140 ](https://github.com/tannerlinsley/react-query/pull/3140)

Pull request description:
fix(queryObserver): fix data stability
by making sure to memoize the structurally shared version of data rather than the raw one

closes #3181 

Patch:
diff --git a/src/core/queryObserver.ts b/src/core/queryObserver.ts
index 652fb2a25..77480e5c3 100644
--- a/src/core/queryObserver.ts
+++ b/src/core/queryObserver.ts
@@ -507,13 +507,13 @@ export class QueryObserver<
       } else {
         try {
           data = options.select(state.data)
+          if (options.structuralSharing !== false) {
+            data = replaceEqualDeep(prevResult?.data, data)
+          }
           this.previousSelect = {
             fn: options.select,
             result: data,
           }
-          if (options.structuralSharing !== false) {
-            data = replaceEqualDeep(prevResult?.data, data)
-          }
           this.previousSelectError = null
         } catch (selectError) {
           getLogger().error(selectError)


Test patch:
diff --git a/src/react/tests/useQuery.test.tsx b/src/react/tests/useQuery.test.tsx
index f93ac90ec..3cc2facc9 100644
--- a/src/react/tests/useQuery.test.tsx
+++ b/src/react/tests/useQuery.test.tsx
@@ -4048,6 +4048,52 @@ describe('useQuery', () => {
     await waitFor(() => rendered.getByText('Data: selected 3'))
   })
 
+  it('select should structually share data', async () => {
+    const key1 = queryKey()
+    const states: Array<Array<number>> = []
+
+    function Page() {
+      const [forceValue, forceUpdate] = React.useReducer(prev => prev + 1, 1)
+
+      const state = useQuery(
+        key1,
+        async () => {
+          await sleep(10)
+          return [1, 2]
+        },
+        {
+          select: res => res.map(x => x + 1),
+        }
+      )
+
+      React.useEffect(() => {
+        if (state.data) {
+          states.push(state.data)
+        }
+      }, [state.data])
+
+      return (
+        <div>
+          <h2>Data: {JSON.stringify(state.data)}</h2>
+          <h2>forceValue: {forceValue}</h2>
+          <button onClick={forceUpdate}>forceUpdate</button>
+        </div>
+      )
+    }
+
+    const rendered = renderWithClient(queryClient, <Page />)
+    await waitFor(() => rendered.getByText('Data: [2,3]'))
+    expect(states).toHaveLength(1)
+
+    rendered.getByRole('button', { name: /forceUpdate/i }).click()
+
+    await waitFor(() => rendered.getByText('forceValue: 2'))
+    await waitFor(() => rendered.getByText('Data: [2,3]'))
+
+    // effect should not be triggered again due to structural sharing
+    expect(states).toHaveLength(1)
+  })
+
   it('should cancel the query function when there are no more subscriptions', async () => {
     const key = queryKey()
     let cancelFn: jest.Mock = jest.fn()