T
TanStack3y ago
passive-yellow

setQueryData and structuralSharing

Hey! I have a question about structural sharing behavior when calling setQueryData manually. I have a query which I want to be updated only manually by setQueryData, so I set staleTime and cacheTime to Infinity. I also set structuralSharing to false, so my component always react to data changes after manual setQueryData call, even if updated data is exactly the same as previous one. The thing is that it doesn't. If new data is new object (made by spread operator), but is deeply equal to old data, data property returned from this query still has the same reference and it doesn't trigger useEffect(..., [data]). I also tried to use structuralSharing as the function, so I check if it's called properly. But it never gets called. However, if I change any property inside my data when calling setQueryData, data from query has new reference and all works as expected. I really would like to understand how setQueryData and structuralSharing works together under the hood. I have pretty big object under this query key and I would like to make sure then structural sharing is really disabled and replaceEqualDeep never gets called for this query. And btw thanks for this incredible library 🙏
9 Replies
passive-yellow
passive-yellowOP3y ago
So basically I would like to understand if comparison for structural sharing is executed after calling setQueryData? Or is it run only on useQuery level for fetch function result?
generous-apricot
generous-apricot3y ago
if structuralSharing is turned off, it should work. can you show a reproduction?
passive-yellow
passive-yellowOP3y ago
sure! So basically this example shows more or less what I'm doing in my app: https://codesandbox.io/s/structural-sharing-issue-iggui7?file=/src/index.jsx The query I mentioned is wrapped with additional abstraction hook useUserQuery which is used in multiple places in the app. It also returns the method for updating data which basically calls setQueryData under the hood. If you press the button in the example, it calls this function and updates data with new, but deeply equal object. On first app launch it works properly. useEffect is triggered and there is a log from structuralSharing function. However, when you refresh sandbox browser and data is restored from async storage it will not work anymore. It has sth to do with persistence - after disabling it by commenting persistQueryClient everything works as expected. Both in the example and in my app maybe I have sth wrong with persister config and it leads to this weird issue. I recently migrated from v3 and didn't switch to PersistQueryClientProvider
generous-apricot
generous-apricot3y ago
can you try switching to PersistQueryClientProvider? also try removing initialData. just trying to narrow it down sandbox doesn't start up for me ... codesandbox has been really rough the last couple of days 😅
passive-yellow
passive-yellowOP3y ago
ah that's bad, I tried it in incognito and all worked properly though. So I played with initial data - it seems to don't have any impact on that but I also switched to PersistQueryClientProvider I used in two ways: 1. Wait with rendering whole app until data is restored
export default function App() {
const [isRestored, setIsRestored] = useState(false);

return (
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister }}
onSuccess={() => {
setIsRestored(true);
}}
>
{isRestored ? <User /> : null}
<ReactQueryDevtools />
</PersistQueryClientProvider>
);
}
export default function App() {
const [isRestored, setIsRestored] = useState(false);

return (
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister }}
onSuccess={() => {
setIsRestored(true);
}}
>
{isRestored ? <User /> : null}
<ReactQueryDevtools />
</PersistQueryClientProvider>
);
}
2. Don't wait and render immediately
export default function App() {
return (
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister }}
>
<User />
<ReactQueryDevtools />
</PersistQueryClientProvider>
);
}
export default function App() {
return (
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister }}
>
<User />
<ReactQueryDevtools />
</PersistQueryClientProvider>
);
}
for 2 it seems to be always working. Although it mounts and renders whole tree before data is restored which I really wanted to avoid for 1 the issue is the same - initial launch works ok. But then every refresh where data is restored it breaks - my structural sharing function is never called and data points to the same reference
generous-apricot
generous-apricot3y ago
interesting. might be necessary to file an issue then. FYI, it's on purpose that the App renders while restoring from the persisters, mainly due to ssr reasons. The app will render with status: loading and fetchStatus: idle so you know it's not fetching data. Also, since you are passing initialData, you would see a render with that data first (so status would actually be success) before it gets "replaced" with data from the persister you can also check for useIsRestoring to see if you are restoring - don't need separte state for that 🙂
passive-yellow
passive-yellowOP3y ago
Oh I see the point with the SSR, thanks for the explanation! But I'm actually making react-native app so I don't need to worry about ssr. And in that case it actually feels better to prevent main app rendering before restore - it's all happening under the splash screen anyway. Do you see any potential issues with that? Right, I tried but I would have to move this hook to component one lever lower to get proper context from PersistQueryClientProvider. If I use it in App it's always false.
With that solution I can keep this config in one place, but I guess it doesn't make big difference 😃 I'll create an issue later then. Thanks!
generous-apricot
generous-apricot3y ago
no it's fine if you want to do that. In that case, you can also stick to your old approach, but make sure that you:
await persistQueryClient(...)
await persistQueryClient(...)
and only then render your app.
passive-yellow
passive-yellowOP3y ago
yeah I did that before when using v3 and still do that after migration. But issue mentioned in this post is exactly the same 😦

Did you find this page helpful?