Defining custom merge function to resolve InMemoryCache merge in GraphQL - javascript

I'm getting a warning:
Cache data may be lost when replacing the parts field of a Query object.
To address this problem (which is not a bug in Apollo Client), define a custom merge function for the Query.parts field, so InMemoryCache can safely merge these objects:
existing: [{"__ref":"Part:53"},{"__ref":"Part:55"},{"__ref":"Part:56"},{"__ref":"Part:57"},{"__ref":"Part:58"}]
incoming: [{"__ref":"Part:53"},{"__ref":"Part:55"},{"__ref":"Part:56"},{"__ref":"Part:57"}]
Now here is my Part type:
type Part {
id: ID!
created_at: DateTime!
updated_at: DateTime!
partName: String
partDescription: String
partQuantity: Long
usePercentage: Boolean
partPercentage: Float
type: String
published_at: DateTime
products(sort: String, limit: Int, start: Int, where: JSON): [Product]
partImage(sort: String, limit: Int, start: Int, where: JSON): [UploadFile]
stockevents(sort: String, limit: Int, start: Int, where: JSON): [Stockevent]
}
This warning triggers after I remove one part using mutation to delete a single part. Here it is:
const [partDelete] = useMutation(DELETE_PART, {
update(cache, { data }) {
const newData = Object.values(data)
const refresh = newData.map(name => name.part)
const refined = refresh.map(item => item.id)
cache.evict({
id: cache.identify({
id: refined.id
})
})
cache.writeQuery({
query: GET_PARTS
})
},
refetchQueries: [
{ query: GET_PARTS }
]
})
I am passing payload in a separate function and everything works but I keep getting this cache warning so I want to deal with it now.
I've went with updating InMemoryCache in my index.js but it still doesn't work:
export const client = new ApolloClient({
link,
cache: new InMemoryCache({
typePolicies: {
Part: {
merge(existing = [], incoming = []) {
return [...existing, ...incoming];
}
}
}
})
});
I've also tried to return only ...incoming but nothing different happens.
Thanks in advance, cheers!

The issue was in the structure of InMemoryCache config. After I changed it to this it worked:
export const client = new ApolloClient({
link,
cache: new InMemoryCache({
typePolicies: {
Query: {
Part: {
parts: {
fields: {
merge(existing, incoming) {
return incoming;
}
}
}
}
}
}
})
});
I have also removed update option from the mutation that includes evict and modify.

when you see this warning:
To address this problem (which is not a bug in Apollo Client), define a custom merge function for the Query.your_query_name field, so InMemoryCache can safely merge these objects:
try this for shorten:
const client = new ApolloClient({
uri: "your_API_link",
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
your_Query_Name: {
merge: (existing = [], incoming) => {
return incoming;
},
},
},
},
},
}),
});

Related

Apollo GraphQL merge cached data

I have a page that consists of 2 components and each of them has its own request for data
for example
<MovieInfo movieId={queryParamsId}/>
const GET_MOVIE_INFO = `gql
query($id: String!){
movie(id: $id){
name
description
}
}`
Next component
<MovieActors movieId={queryParamsId}/>
const GET_MOVIE_ACTORS = `gql
query($id: String!){
movie(id: $id){
actors
}
}`
For each of these queries I use apollo hook
const { data, loading, error } = useQuery(GET_DATA, {variable: {id: queryParamsId}}))
Everything is fine, but I got a warning message:
Cache data may be lost when replacing the movie field of a Query object.
To address this problem (which is not a bug in Apollo Client), either ensure all objects of type Movie have IDs, or define a custom merge function for the Query.movie field, so InMemoryCache can safely merge these objects: { ... }
It's works ok with google chrome, but this error affects Safari browser. Everything is crushing. I'm 100% sure it's because of this warning message. On the first request, I set Movie data in the cache, on the second request to the same query I just replace old data with new, so previous cached data is undefined. How can I resolve this problem?
Here is the same solution mentioned by Thomas but a bit shorter
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
YOUR_FIELD: {
// shorthand
merge: true,
},
},
},
},
});
This is same as the following
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
YOUR_FIELD: {
merge(existing, incoming, { mergeObjects }) {
return mergeObjects(existing, incoming);
},
},
},
},
},
});
Solved!
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
YOUR_FIELD: {
merge(existing = [], incoming: any) {
return { ...existing, ...incoming };
// this part of code is depends what you actually need to do, in my
case i had to save my incoming data as single object in cache
}
}
}
}
}
})
});
The other answers still work, but as of Apollo Client >= 3.3 there's an easier option that doesn't require specifying specific fields or a custom merge function. Instead, you only have to specify the type and it will merge all fields for that type:
const cache = new InMemoryCache({
typePolicies: {
YOUR_TYPE_NAME: {
merge: true,
}
}
});
From your example query, I'd guess that an id field should be available though? Try requesting the ID in your query, that should solve the problem in a much more ideal way.
Had same issue with inconsistency of data values vs. our schema. A value type within an entity was missing the id value. Caused by an incomplete data migration.
Temporary solution:
const typePolicies = {
PROBLEM_TYPE: {
keyFields: false as false,
},
PARENT_TYPE: {
fields: {
PROBLEM_FIELD: {
merge: true
}
}
}
}

GraphQL Node.js: determine what types are used within a query

Given I have a schema that has types: User, Comment, Post, Image; Is it possible to determine the GraphQL types being used in a query, given the query and schema?
e.g. if a client had a query
{
user(userName: "username") {
email
comments
}
}
In this case, the query has types User and Comment. Is it possible to determine the programmatically using either the graphql-js or graphql packages for node.js?
For anyone else who runs into this, I found the answer in visit and TypeInfo
Here's a function that takes the GraphQL query (document) and a schema and returns which data types from the schema are being used.
import { visit } from 'graphql/language/visitor'
import { parse } from 'graphql/language'
import { TypeInfo, visitWithTypeInfo } from 'graphql'
import { schema as _schema } from '../src/schema/schema'
const getQueryTypes = (schema, query) => {
const typeInfo = new TypeInfo(schema)
var typesMap = {}
var visitor = {
enter(node) {
typeInfo.enter(node)
typesMap[typeInfo.getType()] = true
},
leave(node) {
typesMap[typeInfo.getType()] = true
typeInfo.leave(node)
}
}
visit(parse(query), visitWithTypeInfo(typeInfo, visitor))
return Object.keys(typesMap)
}
const _query = `
query {
...
}
`
console.log(getQueryTypes(_schema, _query))
Given a valid document string representing some GraphQL operation, you can parse the string into an AST.
import { parse, validate } from 'graphql'
const document = parse(someDocumentString)
// if you want to validate your document to verify it matches your schema
const errors = validate(schema, document)
AFAIK, there's no utility function for getting an array of the types in a document, if that's what you're asking for, but you can just traverse the AST and gather whatever information from it. As an example, here's how GraphiQL does just that to generate a map of variables to their corresponding types:
import { typeFromAST } from 'graphql'
export function collectVariables(schema, documentAST) {
const variableToType = Object.create(null);
documentAST.definitions.forEach(definition => {
if (definition.kind === 'OperationDefinition') {
const variableDefinitions = definition.variableDefinitions;
if (variableDefinitions) {
variableDefinitions.forEach(({ variable, type }) => {
const inputType = typeFromAST(schema, type);
if (inputType) {
variableToType[variable.name.value] = inputType;
}
});
}
}
});
return variableToType;
}
You have to create types using GraphQLObjectType, for example:
export default new GraphQLObjectType(
({
name: 'User',
description: 'Represents a User',
fields: () => ({
_id: {
type: GraphQLNonNull(GraphQLString),
resolve: user => user._id,
},
name: {
type: GraphQLNonNull(GraphQLString),
description: 'Name of the user',
resolve: user => user.name,
},
createdAt: {
type: GraphQLString,
description: '',
resolve: user => user.createdAt.toISOString(),
},
updatedAt: {
type: GraphQLString,
description: '',
resolve: user => user.updatedAt.toISOString(),
},
}),
}: GraphQLObjectTypeConfig<User, GraphQLContext>),
);
Then you can use this UserType on another type, by declaring type: GraphQLNonNull(UserType) for example.
https://graphql.org/graphql-js/constructing-types/
Hope it helps :)

Why does my GraphQL query to return one record fail, but my query to find all records works fine?

I have a Mongo database with a collection called 'words' which contains documents like this:
{
_id: "xxxx",
word: "AA",
definition: "Cindery lava"
}
I have a node app that I am using to query and display information from the words collection, with GraphQL. I have created a GraphQL schema and Mongoose model, as shown below.
// Schema
const WordType = new GraphQLObjectType({
name: 'Word',
fields: () => ({
id: {type: GraphQLID},
word: { type: GraphQLString },
definition: { type: GraphQLString },
})
})
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
detailsForWord: {
type: WordType,
args: {word: {type: GraphQLString}},
resolve(parent, args) {
return Word.find({word: args.word});
}
},
allWords: {
type: new GraphQLList(WordType),
resolve(parent, args) {
return Word.find({}).limit(100);
}
}
}
});
// model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const wordSchema = new Schema({
word: String,
definition: String,
});
My problem is that the "allWords" query works perfectly but the "detailsForWord" does not work at all, and I have no idea why.
In GraphiQL I am using these queries:
{
allWords {
word
definition
}
}
... and
{
detailsForWord(word: "AA") {
word
definition
}
}
The former returns records, but the latter always returns the following in GraphiQL:
{
"data": {
"detailsForWord": {
"id": null,
"word": null,
"definition": null
}
}
}
Any ideas why the "detailsForWord" query is failing?
Obviously find returns an array of documents while findOne returns a single document. Therefore the query might be successful you are getting an array no matter what with find. findOne returns the document you are looking for. Your query didn't fail, it returned a promise with an array.
if you do
resolve(parent, args) {
return Word.find({word: args.word}).then(c=>{console.log(c);return c})
}
You'll see an array containing the document in the console.

Using The GraphQL Args Property In A Mutation

I am making a blog service using express and apollo-express along with mongodb (mongoose).
I made some mutation queries, but I have no success with obtaining the args of a mutation query.
Now I am asking for how I should structure my mutation query in order to make the thing work. thanks.
error:
"message": "Blog validation failed: title: Path title is required., slug: Path slug is required."
the query:
mutation ($input: BlogInput) {
newBlog(input: $input) {
title
slug
}
}
the query variables:
{
"input": {
"title": "ABC",
"slug": "abc"
}
}
part of my graphql schema:
type Blog {
id: ID!
title: String!
slug: String!
description: String
users: [User]!
posts: [Post]!
}
input BlogInput {
title: String!
slug: String!
description: String
}
extend type Mutation {
newBlog(input: BlogInput): Blog
}
part of my resolvers:
import Blog from './blog.model'
export const blogs = async () => {
const data = await Blog.find().exec()
return data
}
export const newBlog = async (_, args) => {
const data = await Blog.create({ title: args.title, slug: args.slug })
return data
}
part of my database schema (mongoose):
import mongoose from 'mongoose'
const Schema = mongoose.Schema
const blogSchema = Schema({
title: {
type: String,
required: true
},
slug: {
type: String,
required: true,
unique: true
},
description: {
type: String
},
users: {
type: [Schema.Types.ObjectId],
ref: 'User'
},
posts: {
type: [Schema.Types.ObjectId],
ref: 'Post'
}
})
export default mongoose.model('Blog', blogSchema)
You've defined your newBlog mutation to accept a single argument named input. From what I can tell, you're correctly passing that argument to the mutation using a variable. Your resolver receives a map of the arguments passed to the field being resolved. That means you can access individual properties of the input object like this:
export const newBlog = async (_, args) => {
const data = await Blog.create({ title: args.input.title, slug: args.input.slug })
return data
}
Note, you may want to make input non-nullable (i.e. set the type to BlogInput!), otherwise your resolver will need to handle the possibility of args.input returning undefined.

GraphQL resolve GraphQLObjectType

I'm using express-graphql along with the following query:
invitations {
sent
received
}
The schema definition (simplified) is as follows:
const InvitationType = new GraphQLObjectType({
name: 'InvitationType',
description: 'Friends invitations',
fields: {
sent: {
type: new GraphQLList(GraphQLString),
description: 'Invitations sent to friends.',
resolve() {
return ['sentA'];
}
},
received: {
type: new GraphQLList(GraphQLString),
description: 'Invitations received from friends.',
resolve() {
return ['receivedA', 'receivedB'];
}
}
}
});
// Root schema
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQueryType',
fields: {
invitations: {
type: InvitationType // no resolve() method here.
}
}
})
});
However the resolve methods are never called for sent and received fields. The query above returns:
{data: {invitations: {sent: null, received: null}}}
Is there any way to resolve the nested fields (sent and received) without defining a resolve() method on parent (invitations) field?
This worked for me! According to GraphQL Documentation, The execution will continue if the resolve method returns a non-scalar. So the following code works:
// Root schema
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQueryType',
fields: {
invitations: {
type: InvitationType,
resolve: () => ({}) // Resolve returns an object.
}
}
})
});
Hope this helps. Cheers!

Categories

Resources