0

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

  • I confirmed that fetchQuery successfully retrieves the profile data.
  • After calling fetchQuery, I checked the cache with queryClient.getQueryData, which shows the data is there, for queryKey=['currentProfile', { uid: [USER_ID]}].
  • After calling fetchQuery, I checked the queryClient's cache with queryClient.getQueryCache().getAll() and discovered that there were 2 caches: one with queryKey=['currentProfile', { uid: [USER_ID]}] and one with queryKey=['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 }] and that's why calling fetchQuery with the correct uid does not work, because it only updates the correct cache, not the undefined one.
  • Calling refetch updates useQuery’s data field, but this adds an unwanted extra API call.

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",
}

1 Answer 1

0

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.