Why is React-Query cache not persisted across launches in a React-Native application?

1k views Asked by At

I couldn’t find much information on tanstack's GitHub issues, blog posts, or YouTube videos

I’ve tried logging the results from async-storage using the default key of the react query cache as a troubleshooting step, but nothing ever comes back, always undefined

Ive configured an async storage persister as per the documentations, but nothing seems to be persisted across cold-starts. In the case of the client-only-cache example below, at the point its created, it exsits, but if the hard is quit from multitasking, and re-opened, the cache data is undefined

Here’s my query client config: https://pastebin.com/wMdcHxsz

import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'
import AsyncStorage from '@react-native-async-storage/async-storage';
import { MutationCache, QueryClient } from '@tanstack/react-query'
import { CrashReporting } from 'instabug-reactnative';
 
export const persister = createAsyncStoragePersister({
  storage: AsyncStorage,
  throttleTime: 500,
  key: "REACT_QUERY_OFFLINE_CACHE",
});
 
export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      networkMode: 'online',
      staleTime: Infinity,
      cacheTime: Infinity, // 24 hours
      // staleTime: Infinity, // longer staleTimes result in queries not being re-fetched as often
      refetchOnReconnect: 'always',
      retry: 3,
      retryDelay: 3000,
      refetchOnWindowFocus: true,
      refetchOnMount: true
    },
    mutations: {
      cacheTime: Infinity,
    }
  },
  mutationCache: new MutationCache({
    onSuccess: (data) => {
      console.log('[ Mutation Cache Processed ]:', data)
    },
    onError: (error) => {
      console.log('[ Mutation Cache Error ]:', error)
      CrashReporting.reportError(error);
      throw error
    }
  })
});

Here’s my App.tsx (sans imports): https://pastebin.com/WPkgWLMJ

function App(): JSX.Element {
  useEffect(() => {
    SplashScreen.hide();
 
    onlineManager.setEventListener((setOnline) => {
      return NetInfo.addEventListener((state) => {
        setOnline(!!state.isConnected);
      });
    });
  }, []);
 
  return (
    <ThemeProvider theme={theme}>
      <DevToolProvider>
        <QueryErrorResetBoundary>
          {({ reset }) => (
            <ErrorBoundary onReset={reset}>
              <PersistQueryClientProvider
                client={queryClient}
                persistOptions={{
                  persister,
                  maxAge: Infinity
                }}
                onSuccess={() => {
                  // resume mutations after initial restore from localStorage was successful
                  queryClient.resumePausedMutations().then(() => {
                    queryClient.invalidateQueries();
                  });
                }}>
                <NavigationContainer
                  ref={navigationRef}
                  fallback={
                    <View
                      style={{
                        justifyContent: 'center',
                        alignItems: 'center'
                      }}>
                      <ActivityIndicator animating={true} />
                    </View>
                  }>
                  <RootNavigator />
                </NavigationContainer>
                <ConnectionLostAlert />
                <EnvFlag />
              </PersistQueryClientProvider>
            </ErrorBoundary>
          )}
        </QueryErrorResetBoundary>
      </DevToolProvider>
    </ThemeProvider>
  );
}
 
export default CodePush(codepushConfig)(App);

Heres an example of setting a client-only query cache (no api calls reset this cache):

import { useMutation, useQueryClient } from '@tanstack/react-query';
import { QueryKeys } from '../queries/queryKeys';
import { ProjectJob } from '../ProjectJobsService/types';

export default function useCreateMissingPhotos() {
  const queryClient = useQueryClient();

  queryClient.setMutationDefaults([QueryKeys.MissingPhotos], {
    mutationFn: async () => {} // This is a client-only cache, we don't want to do anything when network is restored
  });

  return useMutation({
    mutationKey: [QueryKeys.MissingPhotos],
    onMutate: async (data: ProjectJob) => {
      await queryClient.cancelQueries([QueryKeys.MissingPhotos]);

      const previousState = queryClient.getQueryData([QueryKeys.MissingPhotos]);

      queryClient.setQueryData(
        [QueryKeys.MissingPhotos],
        (old: ProjectJob[] | undefined) => (old ? [...old, data] : [data])
      );

      return { previousState };
    },
    onError: (err, newState, context) => {
      queryClient.setQueryData(
        [QueryKeys.MissingPhotos],
        context?.previousState
      );
    },
    onSettled: (data, error, newState) => {
      queryClient.invalidateQueries({
        queryKey: [QueryKeys.MissingPhotos]
      });
    }
  });
}

Not sure what’s wrong here, any advice would be appreciated

1

There are 1 answers

3
VonC On

Persisting React Query's cache across app launches in a React Native environment requires a correct setup of asynchronous storage along with React Query. From your configuration, you have attempted to set up @tanstack/query-async-storage-persister for this purpose, which is a good step.

However, if the cache is not being persisted across launches, check first that Async Storage is correctly installed and configured in your React Native project. It might be helpful to test Async Storage independently of React Query to confirm that it is working as expected. Check that @react-native-async-storage/async-storage is installed in your project (npm list @react-native-async-storage/async-storage or yarn list --pattern @react-native-async-storage/async-storage in your project directory).
And create a simple function to set, get, and remove a value in Async Storage.
For instance:

import AsyncStorage from '@react-native-async-storage/async-storage';

async function testAsyncStorage() {
  try {
    // Set a value
    await AsyncStorage.setItem('testKey', 'testValue');
    console.log('Value set successfully');

    // Get the value
    const value = await AsyncStorage.getItem('testKey');
    console.log('Retrieved value:', value);  // Should log: 'Retrieved value: testValue'

    // Remove the value
    await AsyncStorage.removeItem('testKey');
    console.log('Value removed successfully');

    // Try to get the value again (should be null)
    const nullValue = await AsyncStorage.getItem('testKey');
    console.log('Value after removal:', nullValue);  // Should log: 'Value after removal: null'

  } catch (error) {
    console.error('Async Storage test failed:', error);
  }
}

// Call the function to test Async Storage
testAsyncStorage();

After that:

  • In the createAsyncStoragePersister method, you have specified a key of "REACT_QUERY_OFFLINE_CACHE". Ensure that this key does not collide with any other keys used in Async Storage in your application.
  • In your queryClient configuration, you have set cacheTime to Infinity. While this should keep the data in cache, it might be worth experimenting with finite values to observe any different behavior.
  • In your PersistQueryClientProvider, you have set maxAge to Infinity in persistOptions. Similar to the cacheTime setting, you might want to experiment with finite values.
  • Test the persister outside of the QueryClient and PersistQueryClientProvider context to ensure it is functioning as expected. You might want to manually interact with the persister to save and retrieve a test object to/from Async Storage.
  • Check the error handling logic in your mutationCache and useMutation hook for any issues.

All the above has been done already: async storage works in other areas, have used finite values for maxAge/CacheTime, I have also tried manually calling persistQueryClient on app mount. The query cache mentioned doesn't seem to be affected cross-launch.

Given that Async Storage is functioning correctly in other areas of the application, and various configurations for maxAge and cacheTime have been tested, as well as manual calls to persistQueryClient on app mount have been attempted without success, that should mean there might be an issue specifically related to the interaction between React Query and Async Storage.

Review first the configuration of PersistQueryClientProvider and QueryClient to make sure they align with the documentation and examples provided by React Query and @tanstack/query-async-storage-persister.
I would assume you have the latest stable versions, particularly for @tanstack/react-query, @tanstack/query-async-storage-persister, and @react-native-async-storage/async-storage.

Check if the data being stored and retrieved from the cache is being serialized and deserialized correctly. The data stored in Async Storage needs to be stringified and parsed correctly when retrieved.

I would also log the data being saved to and retrieved from Async Storage to see if it is being stored and retrieved as expected. That includes checking the data just before it is persisted, and immediately after it is retrieved.

If the issue persists, try and create a simplified scenario with minimal configuration to test the persistence of React Query cache across app launches: that should help in isolating the issue.