query icon indicating copy to clipboard operation
query copied to clipboard

Inconsistent `select` behaviour for non-enumerable / symbol properties.

Open ycmjason opened this issue 4 months ago • 1 comments

Describe the bug

When select returns an object with non-enumerable / symbol keys, those entries are dropped after queryClient.setQueryData is called,, but not at initial query.

Your minimal, reproducible example

https://stackblitz.com/edit/vitejs-vite-7n6ndech?file=src%2FApp.tsx

Steps to reproduce

  1. Go to stackblitz example
  2. Observe that result.data.hidden is set to result.data.x as expected:
    result.data.x: INITIAL_VALUE
    result.data.y: INITIAL_VALUE
    result.data.hidden: INITIAL_VALUE
    result.data[S(Symbol)]: INITIAL_VALUE
    
  3. Click SET QUERY DATA button
  4. Observe that result.data.hidden is now undefined:
    result.data.x: UPDATED_VALUE
    result.data.y: UPDATED_VALUE
    result.data.hidden: undefined
    result.data[S(Symbol)]: undefined
    

Expected behavior

I expect select will bahave consistently regardless of how the underlying data is set.

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

  • OS: Mac
  • Browser: Chrome

Tanstack Query adapter

react-query

TanStack Query version

^5.90.2

TypeScript version

No response

Additional context

No response

ycmjason avatar Oct 03 '25 14:10 ycmjason

I think that’s because of structural sharing. When select runs the first time, there is nothing to compare it with, so the result is taken 1:1.

Then, when it runs the second time, we structurally share the result from the first time with the new data. Since structural sharing doesn’t support those values, the algorithm basically stops after the first two values and the rest gets dropped.

So, all in all I think this behaviour is expected and yo’d need to turn off structural sharing.

What we could do is make the process better by logging an error here in dev mode

https://github.com/TanStack/query/blob/a2151d28e1cc2a484634b93841d08d24ab683886/packages/query-core/src/utils.ts#L376-L379

We could maybe run structural sharing twice in dev mode and expect the results to be referentially equal, like:

const newData = replaceEqualDeep(prevData, data)
const checkData = replaceEqualDeep(newData, newData)

then check for newData === checkData, if that’s not the case log an error. I think this would detect this case.

TkDodo avatar Oct 25 '25 02:10 TkDodo