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:
useCurrentProfile
Hook: Fetches and caches the current user’s profile using useQuery
.fetchCurrentProfile
Function: Fetches the profile data and updates the cache with queryClient.fetchQuery
.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,
};
};
fetchQuery
successfully retrieves the profile data.fetchQuery
, I checked the cache with queryClient.getQueryData
, which shows the data is there, for queryKey=['currentProfile', { uid: [USER_ID]}]
.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.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.
{
"@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",
}
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.