apollo3-cache-persist

repository·master·Indexed 23 days ago

https://github.com/apollographql/apollo-cache-persist

Provides simple persistence for Apollo Client 3.0 cache implementations, including InMemoryCache and Hermes, for Web and React Native platforms. It allows developers to save and restore the Apollo cache across sessions using the persistCache function or the CachePersistor class for fine-grained control. The library includes built-in wrappers for LocalStorage and AsyncStorage, and is compatible with any storage engine that follows the redux-persist API.

Tokens
6.5K
Snippets
13
Records
38
Agent score
76%

What's inside apollo3-cache-persist

  1. Implement a custom persistence trigger

    master

    To control exactly when persistence occurs, you can provide a custom function to the trigger option.

    A custom trigger must follow this signature: (persist: () => void) => (() => void)

    1. It accepts a persist callback function.
    2. It must return a cleanup function that, when called, uninstalls the trigger.
    // Example: A custom trigger that persists every 10 seconds
    const trigger = persist => {
      // Call `persist` every 10 seconds.
      const interval = setInterval(persist, 10000);
    
      // Return function to uninstall this custom trigger.
      return () => clearInterval(interval);
    };
    
    // Usage in configuration:
    // persistCache({ ..., trigger: trigger });
  2. Persist Apollo Cache using persistCache

    master

    To enable persistence, pass your Apollo cache instance and a storage provider to the persistCache function.

    By default, the cache contents are restored asynchronously upon initialization and persisted upon every write to the cache (using a short debounce interval).

    Important: You should await the persistCache call before instantiating your ApolloClient. If you do not, queries might execute before the cache has been successfully restored from storage.

  3. Create a custom storage provider wrapper

    master

    If you need to use a storage provider not listed in the natively supported providers, or if a breaking change in a supported provider prevents it from working, you can implement your own wrapper.

    To implement a custom wrapper, follow the pattern established in the AsyncStorageWrapper implementation. Your wrapper must satisfy the storage provider API expected by apollo3-cache-persist (which is compatible with the redux-persist storage engine interface).

  4. Wait for cache restoration before rendering in React

    master

    Since persistCache and persistor.restore() return a Promise, you should await the restoration before initializing your Apollo Client and rendering the application. This prevents the UI from rendering with an empty cache before the persisted data is available.

    In React, the recommended pattern is to use useEffect and useState to manage the client initialization lifecycle.

    import React, { useEffect, useState } from 'react';
    import { ApolloClient, ApolloProvider } from '@apollo/client';
    import { InMemoryCache } from '@apollo/client/core';
    import { LocalStorageWrapper, persistCache } from 'apollo3-cache-persist';
    
    const App = () => {
      const [client, setClient] = useState();
    
      useEffect(() => {
        async function init() {
          const cache = new InMemoryCache();
          await persistCache({
            cache,
            storage: new LocalStorageWrapper(window.localStorage),
          });
          setClient(
            new ApolloClient({
              cache,
            }),
          );
        }
    
        init().catch(console.error);
      }, []);
    
      if (!client) {
        return <h2>Initializing app...</h2>;
      }
    
      return (
        <ApolloProvider client={client}>
          {/* the rest of your app goes here */}
        </ApolloProvider>
      );
    };
    
    export default App;
  5. Migrate or purge cache after a breaking schema change

    master

    Because persisted cache data cannot be easily migrated or transformed, you should manually manage schema versions.

    To handle schema changes:

    1. Track a version string (e.g., SCHEMA_VERSION) in your storage.
    2. On app startup, compare the stored version with your current app's schema version.
    3. If they match, call persistor.restore().
    4. If they do not match, call persistor.purge() to clear the outdated cache and update the stored version.
    import AsyncStorage from '@react-native-community/async-storage';
    import { InMemoryCache } from '@apollo/client/core';
    import { CachePersistor, AsyncStorageWrapper } from 'apollo3-cache-persist';
    
    const SCHEMA_VERSION = '3'; // Must be a string.
    const SCHEMA_VERSION_KEY = 'apollo-schema-version';
    
    async function setupApollo() {
      const cache = new InMemoryCache({...});
    
      const persistor = new CachePersistor({
        cache,
        storage: new AsyncStorageWrapper(AsyncStorage),
      });
    
      // Read the current schema version from AsyncStorage.
      const currentVersion = await AsyncStorage.getItem(SCHEMA_VERSION_KEY);
    
      if (currentVersion === SCHEMA_VERSION) {
        // If the current version matches the latest version, we're good to go and can restore the cache.
        await persistor.restore();
      } else {
        // Otherwise, we'll want to purge the outdated persisted cache
        // and mark ourselves as having updated to the latest version.
        await persistor.purge();
        await AsyncStorage.setItem(SCHEMA_VERSION_KEY, SCHEMA_VERSION);
      }
    
      // Continue setting up Apollo as usual.
    }
  6. Avoid AsyncStorage size limits on Android

    master

    When using AsyncStorageWrapper on Android, be aware that AsyncStorage does not support individual values in excess of 2 MB.

    If your cache requires a maxSize greater than 2 MB (or if you set maxSize to false), you should avoid AsyncStorage and instead use a different provider such as MMKVStorageWrapper or redux-persist-fs-storage to prevent data loss or errors.

  7. Troubleshoot Android `CursorWindow` errors

    master

    If you encounter the error BaseError: Couldn't read row 0, col 0 from CursorWindow..., it is likely due to the 2 MB per key limitation of AsyncStorage on Android.

    Solutions:

    • Set a smaller maxSize in your configuration.
    • Switch to a different storage provider that handles larger data better, such as react-native-mmkv-storage or redux-persist-fs-storage.
  8. Use apollo3-cache-persist on the Web

    master

    On the Web, use LocalStorageWrapper to wrap window.localStorage. Ensure you await persistCache before creating your ApolloClient instance to prevent queries from running before the cache is restored.

    import { InMemoryCache } from '@apollo/client/core';
    import { persistCache, LocalStorageWrapper } from 'apollo3-cache-persist';
    
    const cache = new InMemoryCache({...});
    
    // await before instantiating ApolloClient, else queries might run before the cache is persisted
    await persistCache({
      cache,
      storage: new LocalStorageWrapper(window.localStorage),
    });
    
    // Continue setting up Apollo as usual.
    
    const client = new ApolloClient({
      cache,
      ...
    });