Component not re-rendering on removeQueries()
I have a
FlatList
with items. Lets call them FlatListItem
.
I am using memo
to memoize my Items to prevent them to re-render when the parent changes using export default memo(FlatListItem);
For each FlatListItem
in my list I have to fetch data. In a separate file I define a hook and a query function. Simplified they are looking like this:
In my FlatListItem
I will then use useGetDataQuery
:
When the FlatListItem
is loaded for the first time it all works as expected.
Using !isSuccess
I can show a loading indicator and when loading is done my data is shown.
Now in a different part of my application I want to invalidate the data and trigger a refetch in FlatListItem
. Again, with a loading indicator and when loaded the data should be shown.
I tried to accomplish this by using removeQueries
:
However this is not updating my component. The component will not re-render after the query cache is removed.
I am missing something? How can I properly trigger a re-render of my FlatListItem
?45 Replies
solid-orange•2y ago
you want
resetQueries
, not removeQueries
foreign-sapphireOP•2y ago
This gives me the same issue. The data is indeed invalidated, but the
FlatListItem
is not re-rendering and thus no new data is fetched.solid-orange•2y ago
resetQueries
resets a query to its initial state, then informs active observer about that change, which will re-render them and put them into hard loading state. if you see a refetch, that means it worked already because the re-rendering is what triggers the fetch. resetQueries
itself doesn't trigger a refetch
Again, with a loading indicator and when loaded the data should be shown.on a different note: why not just use the
isFetching
flag to show a loading indicator ?foreign-sapphireOP•2y ago
As I was only using
isSuccess
and data
the components would not rerender on resetQueries
as that does not reset the status
and both stayed the same. removeQueries
completely removes the cache and thus resets the status back to pending, but does not inform observers?solid-orange•2y ago
resetQueries
resets to the initial state. unless you are using initialData
, that is status: 'pending', data: undefined
I still don't understand what you are trying to achive. Yes, removeQueries
does not inform observers.
It's not meant to remove something from the cache that is actively used.foreign-sapphireOP•2y ago
In that case I should see
isSuccess
as false
? Because so far what I see is that using resetQueries
works for my case, but isSuccess
stays true
This will for some reason always show 'success' after running resetQueries
. isFetching
will change from to true
and later back to false
, but the pending
state is never seen. I can't figure out why.solid-orange•2y ago
after resetQueries, isSuccess should definitely not be true, unless you use
initialData
. Should be easy to show in a sandbox if that doesn't behave like that pleaseforeign-sapphireOP•2y ago
After some more testing it seems to be related to
PersistQueryClientProvider
with AsyncStorage
. If I change that to a normal QueryClientProvider
it is working as expected.foreign-sapphireOP•2y ago
I have managed to create a working minimal example showing the issue:
https://github.com/rklomp/query-async-test
GitHub
GitHub - rklomp/query-async-test
Contribute to rklomp/query-async-test development by creating an account on GitHub.
foreign-sapphireOP•2y ago
When I use expo 49 which uses react-native 0.72.6 there is no issues.
solid-orange•2y ago
so it's a rect-native / expo issue ?
foreign-sapphireOP•2y ago
Disregard this, its also present using expo 49 and react-native 0.72.6. Switching to a different expo version installs a new version of expo go and thus clearing async storage. On the first app start the issue is not present, but restarting the app the issue is there.
Running
AsyncStorage.clear();
at the first line of App()
also "fixes" the issue.
Could it be that PersistQueryClientProvider
is loading the initial state from storage and not really resetting the initial state?solid-orange•2y ago
hm, maybe data restored from persistence is treated as initial data, I'd need to check. I'd appreciate a quick codesandbox without react-native because it's easier to debug in teh browser where we also have the devtools available.
foreign-sapphireOP•2y ago
I am only familiar with working with react-native, but will see if I can make something
https://codesandbox.io/p/sandbox/query-async-test-rqf58n
After running one time. Do a refresh and you will see
The first line success should read pending
If you remove the REACT_QUERY_OFFLINE_CACHE and refresh, it is working as expected
solid-orange•2y ago
thank you. I will try to take a look if there's something wrong with the combination of persisters and reset
foreign-sapphireOP•2y ago
Thanks!
I have been testing a bit more. It looks like the whole initial data is the data from when the page was loaded and the persistor was initialized. So resetQueries will reset to that data.
My code sandbox is updated with some changes and the devtools:
https://codesandbox.io/p/sandbox/query-async-test-rqf58n
Looks like hydrate is using
QueryCache.build()
to create queries
https://github.com/TanStack/query/blob/main/packages/query-core/src/hydration.ts#L145-L159
This will create a new Query
:
https://github.com/TanStack/query/blob/main/packages/query-core/src/queryCache.ts#L111-L118
Since state is set, #initialState
will be set to config.state
https://github.com/TanStack/query/blob/main/packages/query-core/src/query.ts#L177solid-orange•2y ago
Interesting - so queries created from persisters see data restored from there as initialData 🤔
foreign-sapphireOP•2y ago
Yes
solid-orange•2y ago
Can you try removeQueries() first followed by resetQueries() ?
foreign-sapphireOP•2y ago
I have made an issue on github as well to track yhus
I am now using removeQueries and lastUpdated from my parent component to trigger a render
That works
But resetQueries would be cleaner
solid-orange•2y ago
removeQueries()
followed by resetQueries()
doesn't work ?
ah no it does not
okay I think we can add an option to resetQueries
to perform a hard reset, which defaults to false, so it will do initialState per default and with a hard reset, it would do the empty loading state ...
on the other hand, I also see some discrepencies between hydrate and e.g. setQueryData
. with hydrate, the state becomes the initial state, wich setQueryData, it does not 🤔
Maybe @Ephem knows if it's the intended behaviour of hydration to put the state we hydrate from into intialState
of the query, as that impacts the reset
method.solid-orange•2y ago
here is what setQueryData does:
- build with empty data
- then setting it, which doesn't affect the initial state
https://github.com/TanStack/query/blob/b67107114ce6ccf0d38c2ab22b3088df19ec119b/packages/query-core/src/queryClient.ts#L184-L186
GitHub
query/packages/query-core/src/queryClient.ts at b67107114ce6ccf0d38...
🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query. - TanStack/query
foreign-sapphireOP•2y ago
If you do that what would be the use of
resetQuery
without it setting to true
? There is now a difference in behavior between whether you use a persistor or not. And the behavior is not predictable anymore as the initialState can be anything after hydration
And what happens if I define initialData
myself. And try a reset after an app restart? Will it ever know the original initialState
?solid-orange•2y ago
if you set initialData, and then call resetQueries, it will reset to that state. That is on purpose
foreign-sapphireOP•2y ago
Also if we use a persistor and restart the app? Or will it be using the state from the app start? I'll have to test that
solid-orange•2y ago
sorry, I'm a bit lost now and don't understand the question fully. Is it about having a persister and initialData supplied ?
oh btw combining refetch + remove works: https://codesandbox.io/p/sandbox/query-async-test-forked-gyqr6v?file=%2Fsrc%2FMyComponent.js%3A17%2C1-22%2C8
foreign-sapphireOP•2y ago
Yes when you have both. I have updated my sandbox to show this. https://codesandbox.io/p/sandbox/query-async-test-rqf58n
When setting
initialData
this will only work on a non-hydrated run. As soon as you refresh the page and the cache is loaded from state, the initialData is gone.solid-orange•2y ago
yes but that makes sense to me. The query is first created by the hydration, and queries created by hydration will (with the current implementation) get that set as initialData. Then, when useQuery comes and sends new initialData, the query already exist, and since initialData is only taken into account at query creation time, it does nothing.
put another way:
initialData
on useQuery
also does nothing if you get data into the cache before calling it by any other means, like by calling prefetchQuery
or by calling queryClient.setQueryData
. So this is on purpose.
the thing that is different for hydration from the other ways is that hydration really sets that data as initial state, while all the other methods create an empty query first (with initial data undefined) and then just regularly perform a setData call. The difference is small and only visible when you call resetQueries
, because with prefetching or setQueryData prefilling, reset will revert back to empty state + loading, while if you initialized with hydration, it will revert back to that state.foreign-sapphireOP•2y ago
Yes indeed. So for
resetQueries
the behavior changes and becomes unpredictable as soon as you move from QueryClientProvider
to PersistQueryClientProvider
solid-orange•2y ago
It's merely a question of what we define as "initial". restoring something from an external storage could very well be seen as this data being seen as initial from when the app loaded
foreign-sapphireOP•2y ago
Yeah you're right. But wouldn't you expect that if you set
initialData
in your useQuery
that if you reset, that initial data will be loaded. Even after a persisted state was loaded?
Just looking at it again, but none of the options is saved to persistent storage. networkMode
, retryDelay
, meta
, etc... Will all take an default value.solid-orange•2y ago
yes, that's on purpose. we only store data, not options
foreign-sapphireOP•2y ago
That means that if I define a query using an option it would also not survive a restart. In that way
initialData
is also an option.
Maybe adding a persistOptions
option that defaults to false
to createSyncStoragePersister
would solve all these things?
Or just only this?continuing-cyan•2y ago
I cant remember why I did that unfortunately, so I wouldnt trust it was for a good reason. 😅
solid-orange•2y ago
k, I might remove it as a bug fix. Need to think about it. Right now hydration is the only way to get a query with initialState so that it resets to that. Might also go the other way and have
setQueryData
also do that. Not sure yet 🙂foreign-sapphireOP•2y ago
I think for me having the option to persist all options of a query (thus including initialData) would be the best solution. That way you can make sure Queries will behave exactly the same after a restart. In the current situation it will loose all options (except the ones set by defaultOptions), and thus the behavior of the query might change completely after a restart.
solid-orange•2y ago
We can't persist options because some are observer level options, and they can also be functions which are not persistable
foreign-sapphireOP•2y ago
Hmm yes that is true
In that case, you could never recreate the same state after a restart using the current way state is hydrated. Another solution would be to only hydrate the state after a query was created by
useQuery
. So as soon as a query in initialized for the first time it will check the persisted cach if a state exists and load that into state. This way you can have the options and initial data set first before hydrating the state.solid-orange•2y ago
if you want that, have a look at the
v5
create_persister. It pretty much does thatforeign-sapphireOP•2y ago
Oh nice. I didnt know there was a different way to persist. I'll have a look at it
foreign-sapphireOP•2y ago
You are referring to
experimental_createPersister
? https://tanstack.com/query/latest/docs/react/plugins/createPersisterexperimental_createPersister | TanStack Query Docs
Installation
This utility comes as a separate package and is available under the '@tanstack/query-persist-client-core' import.
solid-orange•2y ago
Yes
foreign-sapphireOP•2y ago
I tried it out, but in that case the resetQueries is not working at all 😢
I'll have to debug it to see why it does not do that
See my forked sandbox using
experimental_createPersister
https://codesandbox.io/p/sandbox/query-async-test-forked-kzdfdt
Looks like it is reset, becomes stale, but a refetch then immediately retrieves data from persisted storage instead of running the function query.solid-orange•2y ago
Because it will restore every time when it runs the queryFn and there's no data in it. I don't quite understand why you want to restore initially but then want to/ expect a hard loading state ?
foreign-sapphireOP•2y ago
Ive got a list of items. On app start I want to show cached data, but as soon as the list is refreshed from the server, I need to invalidate/remove to data for the items from the cache to make sure I dont show any old data.
I prefer completely removing them from the cache/resetting them to an empty initialState to do that
@TkDodo 🔮 I've been looking at
experimental_createPersister
again to see if this will work for my project. I am now also having issues where I want to have a different staleTime
and gcTime
on queries. Since the options are not persisted when using PersistQueryClientProvider
this will not work across app restarts. They will be reset to default values on hydrate. One thing I noticed when looking at how experimental_createPersister
is implemented is that it does not garbage collect any data from storage when that data is not accessed anymore. This could cause a lot of stale items in storage that will never be garbage collected. I am right here or did I miss something and is there a garbage collector implemented somewhere?