How can I cache nested objects with Apollo Client? - reactjs

I'm using the Contentful GraphQL API to fetch a collection of items, in this example football clubs.
query Clubs($limit: Int!, $skip: Int!) {
clubCollection(limit: $limit, skip: $skip) {
total
items {
name
description
}
}
}
The structure of the response is:
clubCollection: {
items: [{
... array of all the clubs
}]
}
It looks like the Apollo InMemoryCache is only caching the full query in its ROOT_QUERY object. But not each individual club. The setup for the cache looks like this:
new InMemoryCache({
typePolicies: {
Query: {
fields: {
clubCollection: concatContentfulPagination()
},
},
},
})
Does anyone know how I can target the clubs in items so that I can cache each individual club?
EDIT:
Thanks to the answer from #xadm I realised I did not need to extend the InMemoryCache like so:
new InMemoryCache({
typePolicies: {
Query: {
fields: {
clubCollection: {
items: {
},
...concatContentfulPagination()
}
},
},
},
})
But instead add it to the root of the typePolicies based on the type of the object, for me it is Club. When I added that it did work!
new InMemoryCache({
typePolicies: {
Club: {
keyFields: ['name']
},
Query: {
fields: {
clubCollection: concatContentfulPagination()
},
},
},
})

Items should have an id prop requested ... to be normalized - Apollo is normalizing cache GraphQL client
It's required to cache entries/types/subtypes properly.
https://graphql.org/learn/caching/
It, unique key can be id, _id or customized key per type using typePolicies - customizing-identifier-generation-by-type.
https://www.apollographql.com/docs/react/caching/cache-configuration/#default-identifier-generation
In this case (no querable id prop inside items), you should check in API (docs/specs/schema or explore network response body - __typename prop of items object) the type of club items entries (probably Club) and customize cache policies like:
new InMemoryCache({
typePolicies: {
Club: {
keyFields: ['name']
},
... assuming name is unique.

For this pupose you can use reactive variables. That way you can access clubs array anywhere in your code without rerunning query and perform any array operation on it.
Visit this page for more info on it.

Related

How to use RTK Query's 'upsertQueryData' to add a new entry to the cache?

I have a GraphQL query setup using the Redux Toolkit's "RTK Query" data fetching functionality. After a mutation related to this query I want to add the returned data from the mutation to be added to the cache without calling the query to the server again. I used the thunk action creator upsertQueryData from the API slices utilities for this. (Reference Documentation).
So far I was only able to overwrite the complete cache collection collection related to the Query but did not find a way to just add 1 entry. Perhaps someone knows what I'm doing wrong?
The GraphQL Query, that is working fine. It returns a collection of 'sites'.
endpoints: (builder) => ({
getSites: builder.query({
query: () => ({
document: gql`
query MyQuery {
sites {
id
name
description
}
}
`,
}),
}),
...
The mutation with usage of upsertQueryData. This overwrites the whole collection of 'sites' of the cache instead of used adding 1 site. To be clear: When sending the mutation I don't have an id yet, that is returned by the server through the mutation callback.
createSite: builder.mutation({
query: ({name}) => ({
document: gql`
mutation createSite {
createSite(
name: "${name}"
description: "The workspace where Peter works from home in Dordrecht",
) {
site {
id
name
description
}
}
}
`
}),
async onQueryStarted({}, { dispatch, queryFulfilled }) {
const { data } = await queryFulfilled;
const newSiteEntry = data.createSite.site;
sites.util.upsertQueryData('getSites', { newSiteEntry.id }, newSiteEntry);
}
I expect to that it adds 1 date object to the site cache object instead of overwriting it. So you will get something like this in the cache:
sites: [
{id: '1', name: 'Existing site 1', description: 'description 1'},
{id: '2', name: 'Existing site 2', description: 'description 2'},
{id: '3', name: 'New site', description: 'new description'},
]
You generally have a wrong concept of the cache here. Since your getSites endpoint takes no argument and you probably only ever call useGetSitesQuery(), there is only ever one cache entry for that (called getSites(undefined)), and you want to update that existing cache entry with additional lines.
upsertQueryData is for overwriting that whole cache entry with a new value, or in your case, creates completely unrelated cache entries that you will never read from - not what you want to do.
As a result, you want to updateQueryData for that one existing cache entry instead:
dispatch(
api.util.updateQueryData('getSites', undefined, (draft) => {
draft.sites.push(newSiteEntry)
})
)
Keep in mind though that generally we recommend using providesTags/invalidatesTags to automatically refetch other endpoints instead of manually doing optimisistic updates on them.

Apollo Client 3 - how to properly implement optimistic response?

I have some queries and mutations and one mutation updates quite big entity so I want to add optimistic response after mutate function is fired. The thing is that even if I pass to optimisticResponse object, full data that will be also returned when mutation completes the job it does not add it to cache - it seems that data is refreshed when mutation response is ready since having optimistic response or not the time of updating UI is the same so I assume optimistic response does not work.
Some code examples I have:
Mutation:
mutation UpdateList($id: ID, $data: ListData) {
updateList(id: $id, data: $data) {
list_id // 1
name
}
}
action
const [action] = useMutation(mutation_from_above)
// async body function so await can be used
await action({ variables: { id: 1, data: { name: 'secret name' }}, optimisticResponse: {
updateList: {
__typename: 'List',
list_id: 1,
name: 'some updated name until data is back'
}
})
and of course I have updated my id field for typename in the cache config like that:
cache: new InMemoryCache({
typePolicies: {
List: {
keyFields: ['list_id'],
},
},
}),
and it looks pretty simple but for me it does not work. I also checked on API side and response from mutation is the same I pass to the optimisticResponse object. Is there some important point why it is not working ? Can someone explain me what to do in order to get this work ?
Thanks, cheers!

How to use custom field in react admin, insted of { data: [...] }

I'm new in react-admin and I'm trying to create a new admin panel for my old API.
So when my data provider do API calls it causes me this error:
The response to 'getList' must be like { data : [...] }, but the received data is not an array. The dataProvider is probably wrong for 'getList'
The responses of my old API has various data fields like { 'posts': [] } or { 'users': [] }. How can I use these name of fields instead of { 'data': [] } ?
The 'data' in this case just refers to the type of information that should be retuned, not the name of the object.
Within your API, you can simply return a list in the following form:
const posts = [
{
"id":1,
"name":"post1"
},
{
"id":2,
"name":"post2"
},
];
return JSON.stringify(posts);
Then return that 'posts' object in your response and don't forget to set the expected ContentRange headers.
Not sure what language you are using, but the principle above should be easy enough to follow and apply in any language.

React Apollo updating client cache after mutation

I am trying to update my chache after succesfully executing a mutation. Here is my query and mutation:
export const Dojo_QUERY = gql`
query Dojo($id: Int!){
dojo(id: $id){
id,
name,
logoUrl,
location {
id,
city,
country
},
members{
id
},
disziplines{
id,
name
}
}
}`;
export const addDiszipline_MUTATION = gql`
mutation createDisziplin($input:DisziplineInput!,$dojoId:Int!){
createDisziplin(input:$input,dojoId:$dojoId){
disziplin{
name,
id
}
}
}`;
and my mutation call:
const [createDisziplin] = useMutation(Constants.addDiszipline_MUTATION,
{
update(cache, { data: { createDisziplin } }) {
console.log(cache)
const { disziplines } = cache.readQuery({ query: Constants.Dojo_QUERY,variables: {id}});
console.log(disziplines)
cache.writeQuery({
...some update logic (craches in line above)
});
}
}
);
when i execute this mutation i get the error
Invariant Violation: "Can't find field dojo({"id":1}) on object {
"dojo({\"id\":\"1\"})": {
"type": "id",
"generated": false,
"id": "DojoType:1",
"typename": "DojoType"
}
}."
In my client cache i can see
data{data{DojoType {...WITH ALL DATA INSIDE APPART FROM THE NEW DISZIPLINE}}
and
data{data{DisziplineType {THE NEW OBJECT}}
There seems to be a lot of confusion around the client cache around the web. Somehow none of the posed solutions helped, or made any sense to me. Any help would be greatly appreciated.
EDIT 1:
Maybe this can help?
ROOT_QUERY: {…}
"dojo({\"id\":\"1\"})": {…}​​​​​
generated: false​​​​​
id: "DojoType:1"​​​​​
type: "id"​​​​​
typename: "DojoType"​​​​​
<prototype>: Object { … }​​​​
<prototype>: Object { … }
Edit 2
I have taken Herku advice and started using fragment. however it still seems to not quite work.
My udated code:
const [createDisziplin] = useMutation(Constants.addDiszipline_MUTATION,
{
update(cache, { data: { createDisziplin } }) {
console.log(cache)
const { dojo } = cache.readFragment(
{ fragment: Constants.Diszilines_FRAGMENT,
id:"DojoType:"+id.toString()});
console.log(dojo)
}
}
);
with
export const Diszilines_FRAGMENT=gql`
fragment currentDojo on Dojo{
id,
name,
disziplines{
id,
name
}
}
`;
however the result from console.log(dojo) is still undefined.Any advice?
So I think your actual error is that you have to supply the ID as as a string: variables: {id: id.toString()}. You can see that these two lines are different:
dojo({\"id\":1})
dojo({\"id\":\"1\"})
But I would highly suggest to use readFragment instead of readQuery and update the dojo with the ID supplied. This should update the query as well and all other occurrences of the dojo in all your queries. You can find documentation on readFragment here.
Another trick is as well to simply return the whole dojo in the response of the mutation. I would say people should be less afraid of that and not do to much cache updates because cache updates are implicit behaviour of your API that is nowhere in your type system. That the new disziplin can be found in the disziplins field is now encoded in your frontend. Imagine you want to add another step here where new disziplins have to be approved first before they end up in there. If the mutation returns the whole dojo a simple backend change would do the job and your clients don't have to be aware of that behaviour.

React Relay fetch list

I need to fetch list
I have Schema
export const Schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
activities: {
name: 'Activities',
type: new GraphQLList(activityType),
resolve: (root, args, { rootValue }) => {
return User.findById(rootValue.req.user.id)
.populate('activities')
.then((user) => user.activities);
},
},
When I do graphql request using curl and sends
query ActivitiesQuery {
activities {
name
}
}
it gives me
{
"data": {
"activities": [
{
"name": "Eat"
}
]
}
}
Can somebody give me example how can I fetch it using relay ? because all the examples give object and then list like Store: { teas: [...
activities is an example of a plural non-identifying field. It is plural in the sense that it returns a list of things, and non-identifying in the sense that the individual elements of that list are not identified by global IDs.
Relay does not support plural non-identifying fields at the root, but support is coming. Follow along at https://github.com/facebook/relay/issues/112

Resources