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?