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

QueryClient.clear does not trigger a rerender immediately #7761

Closed
mkarajohn opened this issue Jul 18, 2024 · 2 comments
Closed

QueryClient.clear does not trigger a rerender immediately #7761

mkarajohn opened this issue Jul 18, 2024 · 2 comments

Comments

@mkarajohn
Copy link

mkarajohn commented Jul 18, 2024

Describe the bug

Very short version:

Calling QueryClient.clear does not immediatelly trigger a rerender to the component that uses the useQuery hook.

Longer version:

In the repro example we have

  • 2 routes/pages, /logout and /dashboard
  • A component AuthProvider that checks if the user is authenticated, and if they are it fetches the user data, using the useUser hook, and passes it down as context. It also passes down 2 functions, login and logout, each of which makes use of mutateAsync from useLogin and useLogout hooks respectively.
  • An accompanying hook called useAuth that provides the context of AuthProvider
  • A hook, which is called in each page, called useAuthCheck which redirects the user to /dashboard if they are currently on /logout and user data exists (user isAuthenticated) or redirects the user to /logout if they are currently on /dashboard and no user data exists (user !isAuthenticated)
  • A call to QueryClient.clear inside the onSuccess of the useLogout hook

What is actually be happening

(READ THE WHAT IS SUPPOSED TO BE HAPPENING FIRST)

  • We are on /dashboard and the user clicks the logout button
  • User logs out, QueryClient.clear() and history.replace('/logout') are called
  • The page for /logout renders, due to location changing internally in BrowserRouter
  • The useAuthCheck hook calls useAuth which returns isAuthenticated: true (because AuthProvider has not yet rerendered)
  • Since we are on /logout and isAuthenticated: true the effect in useAuthCheck redirects us back to /dashboard
  • Since AuthProvider rerenders, it now passes the correct context down and it's what the useAuth hook should provide when called.
  • The page for /dashboard renders
  • The useAuthCheck hook calls useAuth which now returns isAuthenticated: false
  • Since we are on /dashboard and isAuthenticated: false the effect in useAuthCheck redirects us back to /logout
  • Nothing else happens, we stay on /logout as logged out users.

Your minimal, reproducible example

https://stackblitz.com/edit/vitejs-vite-cwizyh?file=src%2Fproviders%2FAuthProvider.tsx&view=editor

Steps to reproduce

  • open up the preview of the example repo in its own tab so that yu can use the react devtools
  • open up the dev console
  • click on login
  • enable breakpoints and click logout
  • notice how the AuthProvider is not immediately rerendering after the QueryClient.clear(), leading to a back and forth between /logout and /dashboard

If you go into the code of AuthProvider and comment in the setToken that are commented out and try to reproduce the above behaviour, you will now notice that it works correctly because the AuthProvider rerenders immediately due to changing its state.

Expected behavior

What is supposed to be happening

  • We are on /dashboard and the user clicks the logout button
  • User logs out, QueryClient.clear() and history.replace('/logout') are called
  • AuthProvider rerenders, due to the data of the useUser hook now being undefined from clearing the cache
  • Since AuthProvider rerenders, it now passes the correct context down and it's what the useAuth hook should provide when called.
  • The page for /logout renders
  • The useAuthCheck hook calls useAuth which returns isAuthenticated: false
  • Nothing else happens, we stay on /logout as logged out users.

How often does this bug happen?

Every time

Screenshots or Videos

No videos and screenshots but I have added 2 profiling logs inside the example repo (one with the wrong and one with the correct behaviour) so you can load them in your react profiler and check them

Platform

  • Ubuntu 22.04
  • Chrome 126.0.6478.126

Tanstack Query adapter

react-query

TanStack Query version

5.51.9

TypeScript version

5.5.3

Additional context

No response

@TkDodo
Copy link
Collaborator

TkDodo commented Jul 25, 2024

Calling QueryClient.clear does not immediatelly trigger a rerender to the component that uses the useQuery hook.

That's on purpose, because it would lead to an immediate refetch, putting the component in a hard loading state. If that's what you want, use queryClient.resetQueries instead.

also, I don't understand step 4 in the current behaviour:

The useAuthCheck hook calls useAuth which returns isAuthenticated: true (because AuthProvider has not yet rerendered)

if Logout renders, it calls useAuth, and if useAuth calls useUser under the hood, it should read the correct data. But I guess you are copying data from useUser into a context (which usually isn't good) and that's why you get out of sync - because the AuthProvider is higher up the tree.

All in all, either use resetQueries and prepare for an immediate refetch (which won't happen if you call useUser({ enabled: false }), but this also is a bit smelly), or remove the Auth Context and just read from the query cache (this is the better fix)

@TkDodo TkDodo closed this as not planned Won't fix, can't repro, duplicate, stale Jul 25, 2024
@mkarajohn
Copy link
Author

@TkDodo

Thanks for taking the time! I have since resolved this in a different way, but I did check out the resetQueries suggestion now that I read this.

That's on purpose, because it would lead to an immediate refetch, putting the component in a hard loading state.

Cool, that's good to know, however

If that's what you want, use queryClient.resetQueries instead.

This showcases the exact same behaviour. If you remove the enabled: false from the useUser query and switch clear with resetQueries, there is still no immediate rerender in the Authprovider (you can add a breakpoint to the return statement, and you will see that the breakpoints order shows it does not immediatelly rerender (it should be debugger > debugger > return > debugger, but it's debugger > debugger > debugger > return))

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

No branches or pull requests

2 participants