Inconsistent `select` behaviour for non-enumerable / symbol properties.
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
- Go to stackblitz example
- Observe that
result.data.hiddenis set toresult.data.xas expected:result.data.x: INITIAL_VALUE result.data.y: INITIAL_VALUE result.data.hidden: INITIAL_VALUE result.data[S(Symbol)]: INITIAL_VALUE - Click
SET QUERY DATAbutton - Observe that
result.data.hiddenis nowundefined: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
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.