Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RTK Query returns cached data for different query arguments, unless remount is forced #4575

Open
thomasmarr opened this issue Aug 22, 2024 · 4 comments

Comments

@thomasmarr
Copy link

I do not think this is expected behavior, but could be mistaken. Any advice would be appreciated.

I have query definition like this:

profilePhoto: builder.query<string, string | undefined>({
  query: (entraid_user_id: string) => ({
    url: `users/${entraid_user_id}/photos/240x240/$value`,
    responseHandler: (response) => {
      if (response.ok) return response.blob();
      throw response;
    },
  }),
  transformResponse: (response: Blob) => {
    return URL.createObjectURL(response);
  },
  providesTags: (_, __, entraid_user_id) => [
    { type: "profilePhoto", id: entraid_user_id },
  ],
}),

It's called in a component like this:

const ProfilePhoto = ({ user }: { user: User }) => {
  const { data, isLoading } = useProfilePhotoQuery(user.entraid_user_id);
  // ...

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 of data returned by the query hook is the value for the previously rendered user, and the value of isLoading remains false.

When the network request is rejected (404), still the value of data is for the previously rendered user, and isLoading 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 is true, and when the 404 is returned, data remains undefined and isLoading is false. In this case it doesn't matter whether I set refetchOnMountOrArgChange - 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.

@markerikson
Copy link
Collaborator

markerikson commented Aug 22, 2024

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 progress
  • data 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 instead

For more details, see:

@thomasmarr
Copy link
Author

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.

@phryneas
Copy link
Member

phryneas commented Aug 23, 2024

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.

@rjgotten
Copy link

rjgotten commented Sep 4, 2024

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants