Closed thomasmarr closed 1 month ago
Those are both expected, yes:
isLoading
is only true on the very first request for this hook. All other times it's false. You probably should be looking at isFetching
, which is true every time any request for this hook is in progressdata
always shows the last successful result, so that you can do something like showing existing data while dimming the UI to indicate a request is in progress. If you absolutely need the value that corresponds with the current args, even if it's not available yet, use currentData
insteadFor more details, see:
Ok, thanks for taking the time to reply (especially given it's explained in the docs - I swear I looked before posting the issue).
It still feels counterintuitive to me, as though the default behavior should be the other way around. I would have thought the most common use case for a query hook with args is to return a different resource based on the provided arg.
// first render
const { data, isLoading } = useGetPokemonQuery(pikachu.id) // <- data is pikachu
// second render
const { data, isLoading } = useGetPokemonQuery(bulbasaur.id) // <- data is still pikachu? this is unexpected
Anyway I'm nitpicking. This issue can be closed. Thanks again.
The idea here is to prevent big layout changes. Usually it's preferrable to keep old data on the screen for a second longer instead of having the whole screen replaced with a spinner and then immediately have the data flicker in 50ms later. It's also what React Suspense does (read: will do when it's finally officially supported on the client) - old data stays on new screen until new data is fetched.
@phryneas It's also what React Suspense does (read: will do when it's finally officially supported on the client) - old data stays on new screen until new data is fetched.
No. It's not.
<Suspense>
renders the fallback content should any component within it suspend.
If you want to keep showing existing content you need to actually do work for that and need to arrange code to use the useDeferredValue
hook or leverage startTransition
. The default is to show the fallback content - i.e. 'the spinner' in this story.
I understand why it is done this way (to embrace showing the cached data while fetching things in background) but also it causes A LOT of headache sometimes. For example I have to wrap some hooks with code like this:
export const useGetNodesQuery = (itemId?: string): CurrentItemReturnType => {
const { data: currentItem, isLoading: isLoadingCurrentItem } =
useGetItemQuery(itemId!, {
skip: !itemId,
});
// when query is dispatched with a new argument, the query hook returns previous data,
// and it also not marked as isLoading if there is any previous data
// this is done to embrace showing cached data
// but for this specific case we do not want to use the "previous" data as it is irrelevant
if (currentItem && currentItem.id !== Number(itemId)) {
return {
currentItem: undefined,
isLoadingCurrentItem: true,
currentItemId: itemId!,
};
}
return {
currentItem,
isLoadingCurrentItem,
currentItemId: itemId!,
};
};
I do not want to refetchOnMount because, well, I want to keep the cached data, I just do not want the stale data
to be available when it is completely irrelevant for me.
Having some flag to accommodate these cases would be helpful. I'd expect RTKQ to be able to simply treat arg change as working with a new query, i.e. trigger the isLoading state, give data as undefined.
@markerikson can it be reconsidered? Or maybe there is something already that handles this use case?
@maksnester have you checked out currentData
, mentioned in Mark's comment? it'll always match the current arg, or be undefined if it's still fetching.
You also might want isFetching
rather than isLoading
.
Yeah. isLoading
vs isFetching
is admittedly something people get confused over, but most of the time you really want isFetching
:
the way i like to think of it is:
isLoading
- we don't have any data to display yet, so display a skeleton (for example)isFetching
- we might have data but we're fetching something else (or refetching the same data), so we could display something less intrusive like a spinner while displaying the older data in the meantime.@EskiMojo14 @markerikson thank you guys, I skimmed through the topic a bit too quickly and didn't notice the currentData
(didn't know about it).
I do not think this is expected behavior, but could be mistaken. Any advice would be appreciated.
I have query definition like this:
It's called in a component like this:
When that component re-renders with a new user prop, (i.e. with a different value for
user.entraid_user_id
), I can see the correct network request firing, but while that request is in flight the value ofdata
returned by the query hook is the value for the previously rendered user, and the value ofisLoading
remainsfalse
.When the network request is rejected (404), still the value of
data
is for the previously rendered user, andisLoading
remains false.So both while the network request is in flight and after it resolves, the other rendered profile details match the newly rendered user, but the profile photo is of the wrong person.
If I set the query option
{ refetchOnMountOrArgChange: true }
this has no effect. Same behavior.If I force a remount by giving the component a
key={user.id}
prop, then it behaves as expected.data
is undefined,isLoading
istrue
, and when the 404 is returned,data
remains undefined andisLoading
isfalse
. In this case it doesn't matter whether I setrefetchOnMountOrArgChange
- it works with or without it.I have lots of experience with tanstack query, but thiis my first time using RTKQuery. Is this expected behavior? Seems a bit counterintuitive to me if it is.
Thanks.