What keyFields value does Apollo Client v3 InMemoryCache use for objects without id or _id?
0 reputation · 18 Feb 2024, 10:40 UTC
0 reputation · 18 Feb 2024, 10:40 UTC
I am configuring Apollo Client v3 InMemoryCache and need to know how cache identity is assigned when a normalized object has neither an id nor an _id field. The documented fallback chain suggests id first, then _id, then storage under the parent path only, but it is unclear whether a custom keyFields policy returning an empty array changes that fallback or produces an unstable key.
This matters for cache-first reads and writeFragment calls where server responses may omit identifiers for embedded entities. Version assumption: @apollo/client v3 with addTypename enabled by default.
Does an empty keyFields array fall back to id and _id, or does the object remain unnormalized? Which read path governs this behavior when the object appears nested inside another entity?
34775 reputation · 18 Feb 2024, 20:54 UTC
When a normalized object has neither an id nor an _id field, Apollo Client v3’s InMemoryCache behaves as follows:
keyFields policy is defined for the type, the cache falls back to the chain: first try id, then _id, and if both are absent it stores the object only under its parent’s path (i.e., it is not given a top‑level cache key).keyFields: [] for that type in a typePolicy, the fallback chain is bypassed. The object is treated as unnormalized: it receives no generated cache key and is reachable only via the parent object’s field.keyFields value for a type is undefined (i.e., “not set”).keyFields is undefined, the cache uses the id → _id → parent‑path fallback.keyFields is defined (even as an empty array), Apollo skips the fallback and uses exactly what you provided.const cache = new InMemoryCache({
typePolicies: {
YourType: { keyFields: [] } // or omit this line to test the default
}
});
id or _id.cache.extract().keyFields is omitted, you will see the object appear as a top‑level entry only when either id or _id is present; otherwise it will be nested under its parent.keyFields: [] is set, the object will never appear as a top‑level entry; it will exist solely under the parent’s field.To be certain which path applies, confirm whether you have any typePolicy (direct or via inheritance) that defines keyFields for the type in question. If such a policy exists, its value determines the behavior; otherwise the default fallback chain is used.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 18 Feb 2024, 11:06 UTC
A useful distinction for the write path you’re asking about: in Apollo Client v3 a defined keyFields replaces the default id → _id fallback entirely, it does not keep the object inline.
keyFields: [] is still a defined policy. The cache computes an ID from the configured fields, which with an empty array resolves to __typename alone. That creates one shared cache entry per type, so multiple instances overwrite each other — a singleton pattern, not unnormalization.
To keep objects embedded under their parent and avoid a top-level key, the documented opt-out is keyFields: false. With no policy at all the fallback is id then _id, otherwise the object stays inline.