react-nativefirebase-authenticationexporeact-querytanstackreact-query

Why doesn't Tanstack React Query useQuery update data after calling queryClient.fetchQuery


I'm using React Query to manage user profiles in my React Native app with Firebase. After authenticating a user, I want to fetch their profile data from my API and update the cache so that other components using currentProfile from this hook are immediately updated.

Here’s a simplified version of my setup:

  1. useCurrentProfile Hook: Fetches and caches the current user’s profile using useQuery.
  2. fetchCurrentProfile Function: Fetches the profile data and updates the cache with queryClient.fetchQuery.

The problem

After calling fetchQuery, the data field in useQuery does not update as expected. It only updates if I call refetch right after fetchQuery, which I want to avoid because it would trigger an additional API call.

import { useQuery, useQueryClient } from '@tanstack/react-query';
import auth from '@react-native-firebase/auth';
import { getProfile as _getProfile } from '@/src/api/auth';

const useCurrentProfile = () => {
  const queryClient = useQueryClient();

  const { data: currentProfile, error, isLoading } = useQuery({
    enabled: !!auth().currentUser?.uid,
    queryKey: ['currentProfile', { uid:  auth().currentUser?.uid}],
    queryFn: () => _getProfile({ uid: auth().currentUser?.uid, context: 'useQuery' }),
  });

  const fetchCurrentProfile = async () => {
    if (!auth().currentUser?.uid) return null;

  const data = await queryClient.fetchQuery({
    queryKey: ['currentProfile', { uid:  auth().currentUser?.uid}],
    queryFn: () => _getProfile({ uid: auth().currentUser?.uid, context: 'fetchQuery' }),
  });

  console.log(
    '[useCurrentProfile] getQueryData',
    queryClient.getQueryData(['currentProfile', { uid:  auth().currentUser?.uid}]),
  );

  // console.log('[useCurrentProfile] getQueryCache', queryClient.getQueryCache().getAll()); // this shows that there are 2 queryKeys: one with ['currentProfile', { uid: [USER_ID]}] and one with ['currentProfile', { uid:  undefined }]. My guess is that when instantiating useQuery, even though the enabled flag is false, it still creates a cache with queryKey = ['currentProfile', { uid:  undefined }].

  // refetchCurrentProfile().then().catch(); // currentProfile updates only after refetch, but this triggers another, unnecessary, API fetch (because `fetchQuery` already made one).


    return data;
  };

  return {
    currentProfile,
    fetchCurrentProfile,
    error,
    isLoading,
  };
};

What I've Tried

Expected Behavior:

After calling fetchCurrentProfile, I expect useQuery to update its data field with the new profile data from the cache without needing an additional API call.

Questions:

  1. Why doesn’t useQuery update data after fetchQuery and setQueryData?
  2. Is there a way to manually trigger a re-render for useQuery to recognize the updated cache, without triggering a new queryFn call?

Additional Info:

{
  "@react-native-firebase/app": "21.3.0",
  "@react-native-firebase/auth": "21.3.0",
  "@tanstack/react-query": "5.59.20",
  "expo": "51.0.39",
  "react": "18.2.0",
  "react-native": "0.74.5",
}


Solution

  • I think your not properly subscribed to the firebase auth state. Your also using two different queryFn's for the one queryKey technically.

    You could try this suggestion I think:

    import { getProfile as _getProfile } from '@/src/api/auth';
    import auth from '@react-native-firebase/auth';
    import { queryOptions, skipToken, useQuery, useQueryClient } from '@tanstack/react-query';
    
    const profileQueryOptions = (uid: string | null, meta: { queryContext: string }) =>
      queryOptions({
        queryKey: ['currentProfile', { uid }],
        queryFn: uid ? (ctx) => _getProfile({ uid, context: ctx.meta?.queryContext }) : skipToken,
        meta,
      });
    
    const useCurrentProfile = () => {
      const [user, setUser] = React.useState();
      const queryClient = useQueryClient();
    
      const { data: currentProfile, error, isLoading } = useQuery(profileQueryOptions(user?.uid ?? null, { queryContext: 'useQuery' }));
    
      const fetchCurrentProfile = async () => {
        const _auth = auth();
    
        if (_auth.currentUser?.uid) return null;
    
        const data = await queryClient.fetchQuery(profileQueryOptions(_auth.currentUser?.uid ?? null, { queryContext: 'fetchQuery' }));
    
        console.log('[useCurrentProfile] getQueryData', queryClient.getQueryData(['currentProfile', { uid: _auth.currentUser?.uid }]));
    
        return data;
      };
    
      function onAuthStateChanged(user) {
        setUser(user);
      }
    
      React.useEffect(() => {
        const subscriber = auth().onAuthStateChanged(onAuthStateChanged);
        return subscriber; // unsubscribe on unmount
      }, []);
    
      return {
        currentProfile,
        fetchCurrentProfile,
        error,
        isLoading,
      };
    };

    I think even with the subscription, you might be able to get rid of fetchCurrentProfile depending on how you use things downstream of this hook.