Apollo InMemory cache type policy for query with list of ids as argument

Hey,

I need help with a very specific usage of Apollo react client and Apollo InMemory cache.

Here’s our setup:

We have the following schema:

type Query {
  cards(ids: [ID!]!): [Card!]!
}

type Card implements Node& {
  id: ID!
  name: String!
}

We are requesting the cards root field with a list of ids:

query CardsQuery($ids: [String!]!) {
  cards(ids: $ids) {
    id
    name
  }
}

This populates the cache with entries for:

  • ROOT_QUERY
    • cards({ ids: [“id1”, “id2”, “id3”] })
  • Card:id1
  • Card:id2
  • Card:id3

We’d like to be able to leverage the cache entries when sending queries on the cards field with a different list of ids
that incluces already present cache entry.

We’ve tried splitting the list with entries in cache in one side and entries to be fetched on the other:

  const client = useApolloClient();
  // find cards ids that are already in cache
  const idsInCache = useMemo(
    () =>
      ids
        .map(id => {
          try {
            return client.readFragment<R>({
              id: `Card:${h.id}`,
              fragment,
              fragmentName,
              variables: variablesForFragmentCache,
            });
          } catch (e) {
            return null;
          }
        })
        .filter(Boolean)
        .map(c => c!.id),
    [client, ids, fragment, fragmentName, variablesForFragmentCache]
  );

  // missing id is the diff between all ids and ids in cache
  const missingIds = useMemo(
    () =>
      diff(
        ids,
        idsInCache
      ),
    [cardHits, idsInCache]
  );

  const variablesForCacheMemo = useMemo(
    () => variablesForCache(idsInCache.sort()),
    [idsInCache, variablesForCache]
  );

  const variablesForNetworkMemo = useMemo(
    () => variablesForNetwork(missingIds.sort()),
    [missingIds, variablesForNetwork]
  );

  // Even though we could already get the cards from cache with client.readFragment,
  // this does not cope well with updates from graphql subscriptions.
  // It's safer to get the cards in a separate query that we know will go straight to cache.
  const { loading: gqlLoadingFromCache, data: dataFromCache } = useQuery<Q, QV>(
    query,
    {
      variables: variablesForCacheMemo,
      skip: idsInCache.length === 0,
    }
  );

  const { loading: gqlLoading, data: dataFromNetwork } = useQuery<Q, QV>(
    query,
    {
      variables: variablesForNetworkMemo,
      skip: algoliaLoading || skipNetwork(missingIds),
    }
  );

  // build the result by aggregating the two queries and putting cards in the requested order
  const cards = useMemo(() => {
    const cardsById = groupBy(
      c => c!.id,
      [...(dataFromCache?.cards || []), ...(dataFromNetwork?.cards || [])]
    );
    return ids
      .map(id => {
        if (cardsById[id]) return cardsById[id][0]!;

        return undefined;
      })
      .filter(Boolean) as R[];
  }, [dataFromCache?.cards, dataFromNetwork?.cards, cardHits]);

The issue with this is that the query that’s supposed to go straight to cache will only do so if and only if
there’s a cards field cache instance with this exact list of ids as argument variables.

Is there a way to write a cache type policy to make this work? Or is there another way to achieve what we’re trying to do?

Using a cache redirect might help! Something like this maybe?

const client = new ApolloClient({
	cache: new InMemoryCache({
		typePolicies: {
			Query: {
				fields: {
					cards: {
						read(existing, { args, toReference }) {
							return args.ids.map(id => toReference({ __typename: 'Card', id }));
						},
					},
				},
			},
		},
	}),
});

Hey,

Thank you for your help!
Unfortunately it does not look like it’s working.
The network query ends up returning me an empty result all the time (and never actually hits the network). Are you sure that read can return an array?

Hey @Dylan_Wulf,

I looked at the code a bit and it does not look like it can support arrays.
Do you know of any other way I could extend the InMemory cache?

@such Ah sorry about that. It’s definitely supposed to support arrays but it looks like there’s a bug preventing this from working: Cache Redirect not causing query to run on partial data when using lists · Issue #9063 · apollographql/apollo-client · GitHub