apollo-client icon indicating copy to clipboard operation
apollo-client copied to clipboard

Updating multiple queries with `cache.writeQuery` with `keyArgs`

Open mitchheddles opened this issue 5 months ago • 3 comments

Hi,

Is there a way to get a list of queries by name in the cache? I have an issue where I'm unable to update a cached query because I don't know what variables have been used to fetch it.

Example:

  • Query to get a list of cards (paginated)
  • Query accepts sort/filter options
  • Query uses keyArgs in type policy to cache queries separately
  • Mutation to create a card
  • Add card to list using cache.writeQuery in the mutation update callback

Here's what my field policy looks like:

const cardsPagination: FieldPolicy<CardCollection> = {
  keyArgs(args) {
    const variables = args as GetCardsQueryVariables;
    return variables.input ? `${variables.input.order_by}${variables.input.order_by_dir}` : '';
  },
  merge(existing, incoming, { readField }) {
    if (!existing) {
      return incoming;
    }

    const pageInfo = { ...existing.pageInfo, ...incoming.pageInfo };
    const edges = uniqueBy([...existing.edges, ...incoming.edges], (edge) =>
      readField('idx', edge.node),
    );

    return { __typename: 'CardCollection', pageInfo, edges };
  },
};

and the mutation update:

update(cache, { data }) {
  const card = data?.cardCreate;

  if (!card) {
    return;
  }

  cache.writeQuery<GetCardsQuery, GetCardsQueryVariables>({
    query: getCardsQuery,
    // Should have variables here...
    data: {
      cards: {
        __typename: 'CardCollection',
        pageInfo: pageInfoStub,
        edges: [{ node: card, cursor: Date.now().toString() }],
      },
    },
  });
},

My current solution is to call cache.writeQuery with all possible variable combinations, but this isn't ideal. Using refetchQueries is not something I consider a "solution", it's a work-around.

Thanks.

mitchheddles avatar Jul 26 '25 23:07 mitchheddles

Hi @mitchheddles, unfortunately, right now, there's no really good solution for this.

You could call getObservableQueries, but that would only give you all currently active observable queries back, not past queries that might have written into your cache.

This is a shortcoming that we're aware of and at some point during 4.x we want to get a solution for it in place that will allow you to access the variables that were used to write a cache field - but as of right now, you'll probably have to stick to your workaround.

phryneas avatar Jul 29 '25 08:07 phryneas

Thanks @phryneas, but this kinda sucks. Is there a recommended approach for storing the variables/queries besides my own cache or context? It feels like a really shit workaround right now, especially for something thats's already stored somewhere in the client...

I understand it's not as simple as making it the loopup public, or adding a new api, but at this point I'm kinda useless for optimistic updates.

mitchheddles avatar Jul 29 '25 10:07 mitchheddles

Hm, the closest to get would be an approach like this:

In your merge function, you could track the variables in storage and then later access them from cache.modify.

const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        cards: {
          merge(existing = [], incoming, { storage, variables }) {
            storage.variables = variables;

           // actual merge logic
            return [...existing, ...incoming];
          },
        },
      },
    },
  },
});

cache.modify({
  fields: {
    cards(existing = [], { storage }) {
      const variables = storage.variables;
	  // do your modification here instead of `client.writeQuery`
      return existing;
    },
  },
});

phryneas avatar Jul 29 '25 11:07 phryneas