apollographql / apollo-cache-persist

🎏 Simple persistence for all Apollo Cache implementations
MIT License
1.39k stars 118 forks source link

Calling client.clearStore() does not reset the persistence layer #504

Open markedwards opened 1 year ago

markedwards commented 1 year ago

The FAQ states:

In some cases like user logout we want to wipe out application cache. To do it effectively with Apollo Cache Persist please use client.clearStore() method that will eventually reset persistence layer.

This does not appear to work. Data remains in AsyncStorage, and is restored by persistCache().

wodCZ commented 1 year ago

The eventually could be the key here. To forcefully clear the storage, call persistor.purge() as illustrated in the example: https://github.com/apollographql/apollo-cache-persist/blob/405de6fb1e963ab50fa30633fb5e000f91d40540/examples/web/src/App.tsx#L100-L105

client.clearStorage() clears the apollo client storage (sets it to an empty object), but in order to write this new state to your storage, persist has to be called on cache-persist. That is controlled by the trigger option, and if you use background trigger, the new empty state isn't persisted until you background your app.

markedwards commented 1 year ago

Okay, that makes sense, but I think it would be helpful if the FAQ mentioned this detail. Its very easy to read that FAQ, come away with the impression that the persistence is purged on clearStore(), and unexpectedly have very incorrect behavior (that is a bit difficult to triage as such as well).

A variant that seems to work well for me is:

useEffect(() => {
  return client.onClearStore(async () => {
    await persistor.purge();
  });
}, []);

I think most would expect this to be the default behavior. In fact, is there actually a use-case for not purging when the store is cleared?

wodCZ commented 1 year ago

is there actually a use-case for not purging when the store is cleared

I'm not aware of such use-case. The only reason I could think of is that purging might be an IO heavy task which could block the main thread, and the app might look frozen once the user taps on sign-out button. That's why the trigger option exists, and there's no exception to it when it comes to persistence (such as "persist when app is in background, except for when the store is cleared, then persist immediately").

I see your point though, it's definitely a tricky situation with non-obvious behaviour. Would you mind submitting a PR rewording the FAQ?

AngelRmrz commented 10 months ago

What if i have a external logout hook, like this one:

import { client } from '@/apollo';
import useAuthStore from '@/state/auth.state';
import useUserStore from '@/state/user.state';

export const useLogout = () => {
    const clearAuthData = useAuthStore((s) => s.clearAuthData);
    const clearUserData = useUserStore((s) => s.clearUserData);
    const handleLogout = async () => {
        clearUserData();
        clearAuthData();

        client?.restore({});
    };

    return {
        handleLogout,
    };
};

How can you export the persistor from the initial config that looks like this:

import React, { useEffect, useState } from 'react';
import { CachePersistor, MMKVWrapper } from 'apollo3-cache-persist';
import { client } from './client.apollo';
import { apolloStorage } from '@/storage';
import { ApolloProvider } from '@apollo/client';

export const CustomApolloProvider: React.FC<React.PropsWithChildren> = ({
    children,
}) => {
    const [isSetClient, setClient] = useState(false);

    useEffect(() => {
        async function init() {
            const cache = client?.cache;
            let newPersistor = new CachePersistor({
                cache,
                storage: new MMKVWrapper(apolloStorage),
                trigger: 'background',
                debug: __DEV__,
            });
            await newPersistor.restore();
            setClient(true);
        }
        init();
    }, []);

    if (!isSetClient) {
        return null;
    }
    return <ApolloProvider client={client}>{children}</ApolloProvider>;
};