Skip to content
This repository has been archived by the owner on Jul 10, 2019. It is now read-only.

Using apollo-cache-persist with apollo-link-state #170

Open
jlietart opened this issue Jan 18, 2018 · 13 comments
Open

Using apollo-cache-persist with apollo-link-state #170

jlietart opened this issue Jan 18, 2018 · 13 comments
Assignees

Comments

@jlietart
Copy link

jlietart commented Jan 18, 2018

Hi guys,

Thanks for the great work with apollo-link-state.

I would like to use it with apollo-cache-persist but when i try on the todo example (https://github.com/apollographql/apollo-link-state/tree/master/examples/todo), it doesn't works:

const cache = new InMemoryCache();

persistCache({
    cache,
    storage: window.localStorage,
}).then(() => {
    const client = new ApolloClient({
        cache,
        link: withClientState({ ...merge(todos, visibilityFilter), cache }),
    });
    render(
        <ApolloProvider client={client}>
            <App />
        </ApolloProvider>,
        document.getElementById('root'),
    );
});

The cache seems to be restored in the logs, but my app doesn't show the added todos after refreshing.

How can i get this work ?

Thanks

@tyler-dot-earth
Copy link

I get similar results in my own application: I can see apolloClient.cache.data has my cached data (before my app is rendered / starts making queries), but doesn't seem to restore it into the app.

One thing I haven't tried is using CachePersistor instead of persistCache.

That being said, improved documentation and examples seem to be needed.

@jlietart
Copy link
Author

@tsnieman Unfortunatly, using CachePersistor doesn't works too.

I found the cause of the problem, it's when using default values with apollo-link-state because it's writing these default values in the cache. What i'm doing for now to solve this problem is to avoid default in the resolvers and using props options into graphql HOC to use default values when it's not existing in the cache.

@KevinDoom
Copy link

@jlietart @tsnieman What's worked for me is using CachePersistor and calling restore() in my app's root component's componentDidMount() method. This way the cache and client are created, apollo-link-state writes its defaults, and then the persisted data is grabbed and written to the cache.

@jakeleboeuf
Copy link

Is using CachePersistor as @KevinDoom described the recommended approach?

@jeffwillette
Copy link

I tried the approach by @KevinDoom but I found that componentDidMount sometimes causes weird behavior because it doesn't run on subsequent renders where the component is not removed from the DOM. I found that putting the CachePersistor into componentWillUpdate makes it behave more naturally

@randytorres
Copy link

@KevinDoom or @deltaskelta Do you have any examples/code snippets I can take a look at?

@jeffwillette
Copy link

I am using gatsby, so the exports.wrapRootComponent looks weird, but you should be able to get the feel of how to lay it out in plain react...

exports.wrapRootComponent = ({ Root }) => {
  // for the purposes of persisting cache, it is very important that this is outside the
  // scope of the wrappedRoot function
  const { client, persistor } = createBrowserClient();
  window.__APOLLO_CLIENT__ = client;

  const wrappedRoot = () => {
    return (
      <ApolloProvider client={client}>
        <CachePersistor persistor={persistor}>
          <Root />
        </CachePersistor>
      </ApolloProvider>
    );
  };

  return wrappedRoot;
};

class PersistorContainer extends React.Component {
  static propTypes = {
    children: PropTypes.object,
    persistor: PropTypes.object,
    setUser: PropTypes.func,
    user: PropTypes.object
  };

  // check the users auth cookie and set the global user state
  checkAuthCookie = () => {
    // do my auth stuff ... and then

      this.props.setUser({
        name: user.GivenName,
        avatarURL: user.AvatarURL,
        email: user.Email,
        ID: user.ID,
        roles: user.Roles
      });
    }
  };

  componentDidMount() {
    this.props.persistor.restore().then(() => {
      if (!this.props.user.ID) {
        this.checkAuthCookie();
      }
    });
  }

  render() {
    if (!this.props.user.ID) {
      this.checkAuthCookie();
    }
    return this.props.children;
  }
}

// set the user in the local state
const setUser = graphql(SET_USER, {
  props: ({ mutate }) => ({
    setUser: ({ name, avatarURL, email, ID, roles }) => {
      mutate({ variables: { name, avatarURL, email, ID, roles } });
    }
  })
});

// graphql user from local state and pass it as props
const getUser = graphql(GET_USER, {
  props: ({ data }) => ({ user: data.user }),
  options: {
    pollInterval: 1000 * 5
  }
});

const CachePersistor = compose(getUser, setUser)(PersistorContainer);

@randytorres
Copy link

randytorres commented Mar 27, 2018

Thanks @deltaskelta That helped me figure it out. I'm also attaching my code as an example for others.
Its not formatting correctly so heres a gist. https://gist.github.com/randytorres/2d8c36f567a1be7ddb89bb7b8ca7929d

@reinvanimschoot
Copy link

Has anyone found a better solution to this?

@ericvera
Copy link

ericvera commented Apr 19, 2018

I had a similar problem that was caused by calling persistCache() before withClientState() which was causing the defaults to overwrite the persisted data. Posting here in case others are running into the same issue. Here is the code I use to initialize my Apollo Client.


export default async function setupApollo () {
  const cache = new InMemoryCache()

  const stateLink = withClientState(
    { resolvers, cache, defaults }
  )

  // NOTE: This must go after the call to withClientState. Otherwise that will overwrite the
  //  cache with defaults.
  await persistCache({
    cache,
    storage: AsyncStorage,
    maxSize: false, // set to unlimited (default is 1MB https://github.com/apollographql/apollo-cache-persist)
    debug: true // enables console logging
  })

  const httpLink = new HttpLink({
    uri: GRAPHQL_SERVER_URL
  })

  const client = new ApolloClient({
    cache,
    link: ApolloLink.from([stateLink, httpLink])
  })

  return client
}

@reinvanimschoot
Copy link

I just realised this as well and now I feel like an idiot. Everything works now :)

@bennypowers
Copy link
Contributor

bennypowers commented Jul 22, 2018

I store a User type via an updateUser(...) @client mutation for the current user session. This is the same type that might come from the server, e.g. connected to a post as in

query Posts {
  id
  author { id ... } # User type
}

And I have some defaults set up in the store for the default user with id __UNAUTHENTICATED_USER__

When I boot up the app, my cache is restored via persistCache, but I end up with the default user, despite having an object cached by ID for the logged-in user in localstorage.

I added some debug logs, and from that it looks like my app-shell component is connecting, then querying for data, before the cache is fully restored

authLink
WebAuth instantiated
stateLink
persistCache
new ApolloClient
my-app connected
renewTokenFromCookieOrSetTimer
[apollo-cache-persist] Restored cache of size 37507
cache persisted undefined
persisted user {id: "0e287b2c-2f4a-4a15-a078-36593ea802f7", ...}
my-app rendered. user: {id: "__UNAUTHENTICATED_USER__", ... }
[apollo-cache-persist] Persisted cache of size 37501

Why, in this case, if a persistCache operation is in progress, does the query not wait to resolve until the restore is complete?
Also, if the cache was updated by restore, why doesn't the app re-render with the updated user
data?
Also, if I wait for boot up to complete, then check the cache for the User again, I'll get __UNAUTHENTICATED_USER__. Why?

@msmfsd
Copy link

msmfsd commented Oct 18, 2018

Just a note that may help someone, I have a isAuth call that is called by home/login screen and also the PrivateRoute component so refresh on private routes auths as well. Instead of doing the restore on root component mount I do it in my async isAuth call:

export const isAuthenticated = async (): Promise<?AuthUserResultType> => {
  try {
    await cachePersistor.restore()
    const token: ?string = await getAuthToken()
    const userData: ?AuthUserResultType = await getUserData()
    if (!!token && !!userData) return userData
    return null
  } catch (error) {
    NotifyError(error)
    return null
  }
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests