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-yellowOP•3y 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•3y ago
if structuralSharing is turned off, it should work. can you show a reproduction?
passive-yellowOP•3y 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 PersistQueryClientProvidergenerous-apricot•3y 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-yellowOP•3y 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
2. Don't wait and render immediately
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 referencegenerous-apricot•3y 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-yellowOP•3y 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
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!
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•3y 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:
and only then render your app.
passive-yellowOP•3y 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 😦