Optimistic updates to ApolloCache are being overwritten by stale data from the server

I have a web app using a NoSQL database (Firestore). I’m syncing the data in this database with a postgres database which in turn is connected to a graphql server (Hasura). ApolloClient is connected to this graphql server.

Part of my application is rendered using ApolloClient, but when updates are made to that section of the application those updates are applied to the NoSQL database and then the changes get picked up and synced to postgres. Understandably, this introduces some latency from when an update gets applied and and when it is synced to ApolloClient (something like 0-7 seconds). Because of this, I need to optimistically update the ApolloClient cache with these updates. Because the updates are not happening via GraphQL Mutations, I need to manually apply the optimistic updates and I can’t use the builtin mutation optimistic update functionality (I use a combination of cache.writeFragment() and cache.modify()).

The problem is, if I manually apply an optimistic update to Apollo (via apollo.cache.modify()) and then the user happens to immediately refetch the data that was optimistically updated (e.g. they navigate to another page in the app which refetches the data that was just manually optimistically updated), it’s possible for the user to pull down the stale, soon-to-be-updated version of the data which overwrites the manual optimistic updates. This results in the UI “flashing”: the optimistic update is applied, then the stale data is fetched which removes the optimistic update, then the “real” data is synced to the graphql server and the query updates again to show the correct values. All in the span of 0-7 seconds.

Is there a suggested solution for dealing with an issue like this in ApolloClient? Basically, manually applying an optimistic update that can’t be overwritten by the server?

I can think of two possibilities, neither of which seem ideal.

  1. Store the optimistic changes in local-only fields and then use a custom field read function to render the desired value (i.e. if a local-only optimistic change for the field exists, render that, if one doesn’t, render the “real” field value). This should work since fetching data from the graphql server won’t overwrite the local-only fields. I think this will be tricky for insertions/deletions from lists though.
  2. Store the optimistic changes outside of ApolloClient and manually merge the optimistic data with the data coming from Apollo.

While these might be the best solutions they don’t seem like ideal solutions. It occurs to me that the problem I’m encountering could, theoretically, be encountered even if I were using GraphQL Mutations and the built in optimistic updates feature of apollo. If a mutation was executed, applying an optimistic update, but then a query was fetched in the background and resolved before the mutation did, it seems possible that that query could overwrite the optimistic update in Apollo. Is this something that happens in Apollo? In practice, it’s possible that, even if this problem is theoretically possible with GraphQL Mutations and optimistic updates, it doesn’t happen in practice. Or maybe Apollo has some built in tools for dealing with this that I’m not aware of?

Any help or advice is appreciated :pray:

Apollo Client supports local state management, and you can store your optimistic changes in local-only fields, which will not be overwritten when data is fetched from the GraphQL server. This approach will work, but as you mentioned, it might get tricky when dealing with lists, especially if you’re adding or removing items from the list.

1 Like

I’ve ended up storing optimistic updates outside of ApolloClient and merging them with the ApolloClient output. It works well enough.