-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[docs] Update cache-configuration keyFields docs #11778
base: main
Are you sure you want to change the base?
Conversation
The docs were missing an explanation of how the keyFields can use false or a function
|
✅ Deploy Preview for apollo-client-docs ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really appreciate the contribution here! Having the keyFields
function example is super helpful and not sure why we didn't have it before.
There are a few inaccuracies though that I'd love to correct before merging. Mind making those changes? I'd be happy to get this in afterwards.
@@ -194,7 +208,8 @@ This example shows a variety of `typePolicies` with different `keyFields`: | |||
``` | |||
Book:{"title":"Fahrenheit 451","author":{"name":"Ray Bradbury"}} | |||
``` | |||
* The `AllProducts` type illustrates a special strategy for a **singleton** type. If the cache will only ever contain one `AllProducts` object and that object has _no_ identifying fields, you can provide an empty array for its `keyFields`. | |||
* The `AllProducts` type illustrates a special strategy for a **singleton** type. If the cache will only ever contain one `AllProducts` object and that object has _no_ identifying fields, you can provide an empty array or `false` for its `keyFields`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's leave this the same as it was before. Perhaps another bullet point stating that keyFields: false
disables normalization? It doesn't have anything to do with singletons though.
Co-authored-by: Jerel Miller <[email protected]>
// Or we can indicate that this type is a Singleton | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a keyFields
function, returning false
would indicate that this particular instance of a type shouldn't be normalized and instead embedded in the parent, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adamesque thats correct! Here is a test that demonstrates returning false
from a keyFields
function. You can see the cache is non-normalized:
apollo-client/src/cache/inmemory/__tests__/policies.ts
Lines 5543 to 5622 in 8bc7d4d
it(`allows keyFields and keyArgs functions to return false`, function () { | |
const cache = new InMemoryCache({ | |
typePolicies: { | |
Person: { | |
keyFields() { | |
return false; | |
}, | |
fields: { | |
height: { | |
keyArgs() { | |
return false; | |
}, | |
merge(_, height, { args }) { | |
if (args) { | |
if (args.units === "feet") { | |
return height; | |
} | |
if (args.units === "meters") { | |
// Convert to feet so we don't have to remember the units. | |
return height * 3.28084; | |
} | |
} | |
throw new Error("unexpected units: " + args); | |
}, | |
}, | |
}, | |
}, | |
}, | |
}); | |
const query = gql` | |
query GetUser($units: string) { | |
people { | |
id | |
height(units: $units) | |
} | |
} | |
`; | |
cache.writeQuery({ | |
query, | |
variables: { | |
units: "meters", | |
}, | |
data: { | |
people: [ | |
{ | |
__typename: "Person", | |
id: 12345, | |
height: 1.75, | |
}, | |
{ | |
__typename: "Person", | |
id: 23456, | |
height: 2, | |
}, | |
], | |
}, | |
}); | |
expect(cache.extract()).toEqual({ | |
ROOT_QUERY: { | |
__typename: "Query", | |
// An array of non-normalized objects, not Reference objects. | |
people: [ | |
{ | |
__typename: "Person", | |
// No serialized units argument, just "height". | |
height: 5.74147, | |
id: 12345, | |
}, | |
{ | |
__typename: "Person", | |
height: 6.56168, | |
id: 23456, | |
}, | |
], | |
}, | |
}); | |
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think thats accurate, but I'd like to verify myself before giving a definitive yes. Intuitively it would make sense that if we don't get an identifier for a particular store object that we'd consider it non-normalized, but again, let me check to make sure thats accurate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adamesque just tried this out and I can confirm it seems that any falsey value will be treated as false
and won't normalize the record.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, thanks! I don't know exactly why ReturnType<IdGetter>
is important to include in the return type union but it would definitely be clearer if it was just KeySpecifier | false
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree! I'll see if we can simplify that type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ReturnType<IdGetter>
does add undefined
(which is unfortunate because we probably want people to be more explicit with false
) and string
, which is important - the keyFn
function can not only return an array of field names, but also directly an identifier (although I'd advise against using that in many situations and leave the field-reading to the InMemoryCache instead).
I probably wouldn't change the type around to remove undefined
here, since probably a lot of people just skip the return
call in some conditions and that would break a lot of code bases for no very good reason.
@jerelmiller @adamesque I made some doc updates, but feel free to add inline suggestions or more context as needed |
// You can also use a function to determine any of the values above. | ||
// The first argument is the reference to the record to be written, and the second is the runtime context | ||
keyFields: (location, context) => { | ||
if (location.state) { | ||
return ["city, "state", "country"]; | ||
} else { | ||
return ["city, "country"]; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on this comment, I believe this should be context.readField("location")
or context.storeObject.location
and we should steer folks away from using the first arg altogether since it represents the raw server response which includes aliased fields and won't follow refs (aka instead of refs to other nested cache objects you'll only get the fields selected).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see you mention this in a later bullet point, but my worry is that folks will read the code example and not the following notes and be surprised.
The docs were missing an explanation of how the keyFields can use false or a function
I would also appreciate a review from the team as I wrote this up from TS types