Help plz on. Upgrading apollo client
3.7.3 -> 3.8.10
I have this code:
export function useCreateTags() {
const [createMutation] = useMutation(
mutations.CREATE_STORY_TAG_MUTATION,
{
update: (cache, { data: { createStoryTag } }) => {
cache.modify({
fields: {
storyTags( // <-----------------------------Story tags have a problem if I use apollo client 3.8.10
existing: GetStoryTagsResponseType["storyTags"],
{ toReference }
) {
return {
items: [
...existing.items,
toReference(createStoryTag),
],
};
},
},
});
},
}
);
return {
createMutation,
};
}
it works fine with apollo client 3.7.3
but in 3.8.10 I have an error. at this line storyTags(
TS2322: Type
(existing: {
items: StoryTagItem[];
}, {toReference}: ModifierDetails) => {
items: (StoryTagItem | Reference | undefined)[];
}
is not assignable to type
Modifier<Reference | AsStoreObject<{
items: (StoryTagItem | Reference | undefined)[];
}>>
Types of parameters existing and value are incompatible.
Type
Reference | AsStoreObject<{
items: (StoryTagItem | Reference | undefined)[];
}>
is not assignable to type { items: StoryTagItem[]; }
Property items is missing in type Reference but required in type { items: StoryTagItem[]; }
Do I need to work with cach.modify differently now?.. Can you suggest something?...
You can also give me advice on toReference as I don't fully understand it
Hey @drmax24 👋
I believe what you're seeing was introduced by https://github.com/apollographql/apollo-client/pull/10895 which was released in version 3.8.0. I'd suggest using the generic argument on cache.modify here instead of type-casting the field callback argument itself. Also keep in mind that the existing value on cache.modify is going to give you the value written to the cache for that field, not the value returned from the mutation, which may differ and why GetStoryTagsResponseType["storyTags"] (assuming this is a type from the mutation) is incorrect here.
Here is what this would look like using the generic argument:
// Judging by your code sample, this should be the root query type
// since you don't pass an `id` option to `cache.modify`
cache.modify<QueryType>({
// ...
})
Also I'm not sure if you're able to upgrade to a later version, but 3.11.4 released https://github.com/apollographql/apollo-client/pull/11994 which contains one more type fix for array item unions with cache.modify.
You can also give me advice on toReference as I don't fully understand it
toReference takes a raw object and converts it to a Reference object, which is an object with a property __ref with a string value that is the cache key for another object in the cache. This is how the cache is able to deduplicate objects and create relationships between the data. For example, if a User:1 is normalized, the cache data may look something like this:
{
// ROOT_QUERY is where all top-level query data is stored
"ROOT_QUERY": {
"user({\"id\":1})": {
// this is a ref object
__ref: "User:1"
},
// notice this is an array of references, not an array of user objects!
users: [{ __ref: "User:1" }, { __ref: "User:2" }]
},
// All data from any query that returns `User` types are stored here
"User:1": {
__typename: "User",
id: 1,
// ...
},
"User:2": {
// ...
}
}
In your example, you're likely trying to write to an array that is an array of reference objects, hence why the toReference helper is useful. You'll get erratic results if you mix and match references and non-references in the cache in the same array.
toReference handles formatting the reference object and cache keys for you so that you don't need to construct the object yourself:
toReference({ __typename: "User", id: 1, name: "Test" })
// => { __ref: "User:1" }
This is especially important if you've configured alternative keyFields which alters the format of the cache key. For example, keyFields: ['uuid'] in your cache configuration would end up looking something like this:
// ignore the UUID format
{ __ref: "User:{\"uuid\":\"zzzz....\"" }
toReference will help you avoid having to format that string manually because it will look up the keyFields config for that object and format the string accordingly:
toReference({ __typename: "User", uuid: "..." })
// => { __ref: "User:{\"uuid\":\"....\"" }
By the way, if you want to look at the structure of the cache at any given time, you can do so with console.log(client.extract()) which will print out something that looks like the above. Alternatively, you can use the Apollo Client Devtools which will let you inspect and explore the cache.
When you're dealing with data that references other entities in the cache, its important that you return reference objects in cache.modify to ensure the data points to the right place in the cache. Data returned from cache.modify is written as-is so getting the structure right is important.
In the above example, users is an array of references, so any data written to users should write references, not the raw field data, otherwise you end up with a mix and match array of references and raw data. Using the reference here ensures that the each User item points to the normalized User type in the array.
Hope this helps!
I will try suggested apollo version. And see if the old code will work.
@jerelmiller thanks for a good explanation of the new generic of cache.modify<QueryType >.
Are there any best practice for adding a new node as part of an edge in a connection? I.e. When I already have a projectConnection as relayStylePagination, and then create a new project:
update(cache, response) {
cache.modify<Query>({
fields: {
projectConnection(cachedProjectConnection, { toReference }) {
if (!('edges' in cachedProjectConnection)) return cachedProjectConnection // this seems merely to please typescript for Reference
return {
...cachedProjectConnection,
edges: [
...cachedProjectConnection.edges,
{
__typename: 'OrganizationProjectEdge',
cursor: response.data?.createProject?.id,
node: toReference(`Project:${response.data?.createProject?.id}`),
},
],
// cast to the expected type should not be necessary here:
} as typeof cachedProjectConnection
},
},
})
},
If i don't cast as typeof cachedProjectConnection, I get:
Type '(cachedProjectConnection: Reference | AsStoreObject<OrganizationProjectConnection>, { toReference, storeFieldName }: ModifierDetails) => Reference | { ...; }' is not assignable to type 'Modifier<Reference | AsStoreObject<OrganizationProjectConnection>>'.
Type 'Reference | { edges: (OrganizationProjectEdge | { __typename: "OrganizationProjectEdge"; cursor: string | undefined; node: Reference | undefined; })[]; __typename?: "OrganizationProjectConnection" | undefined; pageInfo: PageInfo; }' is not assignable to type 'DeleteModifier | InvalidateModifier | DeepPartialObject<Reference> | DeepPartialObject<AsStoreObject<OrganizationProjectConnection>> | undefined'.
Type '{ edges: (OrganizationProjectEdge | { __typename: "OrganizationProjectEdge"; cursor: string | undefined; node: Reference | undefined; })[]; __typename?: "OrganizationProjectConnection" | undefined; pageInfo: PageInfo; }' is not assignable to type 'DeleteModifier | InvalidateModifier | DeepPartialObject<Reference> | DeepPartialObject<AsStoreObject<OrganizationProjectConnection>> | undefined'.
Type '{ edges: (OrganizationProjectEdge | { __typename: "OrganizationProjectEdge"; cursor: string | undefined; node: Reference | undefined; })[]; __typename?: "OrganizationProjectConnection" | undefined; pageInfo: PageInfo; }' is not assignable to type 'DeepPartialObject<AsStoreObject<OrganizationProjectConnection>>'.
Types of property 'edges' are incompatible.
Type '(OrganizationProjectEdge | { __typename: "OrganizationProjectEdge"; cursor: string | undefined; node: Reference | undefined; })[]' is not assignable to type '(DeepPartialObject<OrganizationProjectEdge> | undefined)[]'.
Type 'OrganizationProjectEdge | { __typename: "OrganizationProjectEdge"; cursor: string | undefined; node: Reference | undefined; }' is not assignable to type 'DeepPartialObject<OrganizationProjectEdge> | undefined'.
Type '{ __typename: "OrganizationProjectEdge"; cursor: string | undefined; node: Reference | undefined; }' is not assignable to type 'DeepPartialObject<OrganizationProjectEdge>'.
Types of property 'node' are incompatible.
Type 'Reference | undefined' is not assignable to type 'DeepPartialObject<Project> | undefined'.
Type 'Reference' has no properties in common with type 'DeepPartialObject<Project>'.