apollographql / apollo-feature-requests

πŸ§‘β€πŸš€ Apollo Client Feature Requests | (no πŸ› please).
Other
130 stars 7 forks source link

Add support for DocumentNode to cache.evict() #440

Open ValentinH opened 5 months ago

ValentinH commented 5 months ago

First, the issue I'm trying to address with this feature request was perfectly stated in 2016 in this comment: image

Today, when we have a mutation that is affecting the result of a query done on another page, I'm using one of these approach:

What I'm looking for is a way to invalidate a set of queries similar to react-query invalidateQueries.

I started to use cache.evict({ fieldName: 'example' }) but the issue is that the fieldName isn't type-safe. Therefore, I built this helper function to do this in a safe way from an array of DocumentNode (generated by graphql-codegen):

const invalidateQueries = (cache: ApolloCache<any>, documentNodes: DocumentNode[]) => {
  const fieldsToInvalidate = documentNodes.flatMap((documentNode) =>
    documentNode.definitions.flatMap((definition) => {
      if (!('selectionSet' in definition)) {
        return [];
      }
      return definition.selectionSet.selections
        .map((selection) => {
          if ('name' in selection) {
            return selection.name.value;
          }
          return null;
        })
        .filter(isPresent);
    })
  );
  fieldsToInvalidate.forEach((fieldName) => {
    cache.evict({
      fieldName,
    });
  });
  // I'm not yet sure if I should call `cache.gc()` here 
};

I can then use it like this:

  const [exampleMutation, result] = useMutation(ExampleMutationDocument);

  const doMutation = async () => {
    await exampleMutation({
      variables: {
        value: 'whatever'
      },
      update: (cache) => {
        invalidateQueries(cache, [ExampleQueryDocument]);
      },
    });
  };

This is working fine on the tests I did but now I'm wondering if cache.evict() could directly accept a DocumentNode (or even an array of nodes) to evict all the fields selected by the queries (this comment was also suggesting this).

Basically being able to do:

  const [exampleMutation, result] = useMutation(ExampleMutationDocument);

  const doMutation = async () => {
    await exampleMutation({
      variables: {
        value: 'whatever'
      },
      update: (cache) => {
        cache.evict(ExampleQueryDocument)
      },
    });
  };

or to be even closer to react-query:

 const [exampleMutation, result] = useMutation(ExampleMutationDocument);

  const doMutation = async () => {
    await exampleMutation({
      variables: {
        value: 'whatever'
      },
      invalidateQueries: [ExampleQueryDocument]
    });
  };
jerelmiller commented 2 weeks ago

Hey @ValentinH πŸ‘‹

Thanks for opening the request and thanks for your patience!

This is an interesting idea, though I'd like to push back a little bit on the idea of a document-based solution. Since InMemoryCache is a normalized cache, there are scenarios that get tricky to handle. For example, what happens if you issue 2 queries that request the same field and you only invalidate one of the queries? Does it partially invalidate the other query? Should it refetch the other query because its partially invalidated? Those are things to think through as well.

I do agree with you that there should be a more robust invalidation API, but I'd prefer to use it on the field-level which fits the normalized cache paradigm more closely. In this case, invalidating a field would mark it as stale and any queries that consume that field would refetch in order to synchronize it with the server. Ideally this becomes the replacement for refetchQueries in the future.

On the TypeScript end, I'd like to see InMemoryCache accept a Schema generic type that has full knowledge of your GraphQL schema types in order to better type APIs such as type policies, cache eviction, etc. We are hoping to get something like this either in 4.0 or a minor version shortly after. I'm hoping this would alleviate some of the type safety issues that currently exist when using the cache.

ValentinH commented 2 weeks ago

Thanks for the detailed answer.

I like the idea of field level invalidation, I'm looking forward to hearing more about it.