6

I want to put the authenticated user in a zustand store. I get the authenticated user using react-query and that causes some problems. I'm not sure why I'm doing this. I want everything related to authentication can be accessed in a hook, so I thought zustand was a good choice.

This is the hook that fetches auth user:

const getAuthUser = async () => {
  const { data } = await axios.get<AuthUserResponse>(`/auth/me`, {
    withCredentials: true,
  });
  return data.user;
};

export const useAuthUserQuery = () => {
  return useQuery("auth-user", getAuthUser);
};

And I want to put auth user in this store:

export const useAuthStore = create(() => ({
  authUser: useAuthUserQuery(),
}));

This is the error that I get:

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons.

you can read about it in the react documentation: https://reactjs.org/warnings/invalid-hook-call-warning.html

enter image description here (I changed the name of some functions in this post for the sake of understandability. useMeQuery = useAuthUserQuery)

I understand the error but I don't know how to fix it.

Arman
  • 720
  • 2
  • 7
  • 18
  • 1
    Please post the error message as text in your question, this makes it easier to search. – Mark Rotteveel Aug 07 '21 at 07:28
  • Ok. I will do it. But the error is very simple and general – Arman Aug 07 '21 at 07:30
  • 1
    Having the error as text in your question serves two purposes: 1) it makes it easier to copy paste (e.g. for a quick google), and 2) asking a question on Stack Overflow is not only for yourself, but also for people in the future with the same error, having the error as text makes it easier to be found through a search engine. – Mark Rotteveel Aug 07 '21 at 07:34

1 Answers1

24

The misunderstanding here is that you don’t need to put data from react query into any other state management solution. React query is in itself a global state manager. You can just do:

const { data } = useAuthUserQuery()

in every component that needs the data. React query will automatically try to keep your data updated with background refetches. If you don’t need that for your resource, consider setting a staleTime.

—-

That being said, if you really want to put data from react-query into zustand, create a setter in zustand and call it in the onSuccess callback of the query:

useQuery(key, queryFn, { onSuccess: data => setToZustand(data) })

edit April 2023:

The advice about the onSuccess callback is not a good one. It seems that I didn't fully understand the consequences at the time of posting this answer.

We have deprecated the callbacks on useQuery and will remove them in the next major version (v5) The callbacks won't reliably run, and if they do, they will run for every instance, which is not what you want if you want to write data to zustand. The better way would be to setup a useEffect:

const { data } = useQuery({ queryKey, queryFn })

React.useEffect(() => {
  if (data) {
    setToZustand(data)
  }
}, [data])

but this still has a bunch of drawbacks, like adding an extra render cycle, and running twice in React18 StrictMode (in dev env). So the initial advice - you don't need to sync data anywhere - still stands.

I wrote a detailed blogpost about this.

TkDodo
  • 20,449
  • 3
  • 50
  • 65
  • 2
    I think in OP's case local/sessionStorage might be useful react-query does't persist data on refresh. Is there a react-query functionality to interact with cookies/local/sessionStorage? – o-az Jan 21 '22 at 07:02
  • we have an experimental local storage persister plugin that will become stable in v4: https://react-query-alpha.tanstack.com/plugins/persistQueryClient – TkDodo Jan 21 '22 at 11:59
  • Assume that you have /login endpoint, which returns you access token and general info about the user. We store access token in local storage and on further page refreshes we'll call another endpoint /me, which returns only general info. Both /login and /me have general info. How should we this general info? We could save it in Zustand store, and update it in /me and /login useQueries(). Suggestions? – GorgeousPuree Dec 08 '22 at 06:59