ApolloClient: Refactor updateQueries to work with this.props.client.mutate - graphql

Note: As the process for handling mutation/query/cache updates is now addressed by update, I am no longer using uodateQueries, making this question no longer relevant.
So, I have a one to many relationship from Posts to Comments:
type Comments {
createdAt: DateTime!
id: ID!
posts: Posts #relation(name: "PostsOnComments")
text: String!
updatedAt: DateTime!
user: String!
}
type Posts {
caption: String!
comments: [Comments!]! #relation(name: "PostsOnComments")
createdAt: DateTime!
displaysrc: String!
id: ID!
likes: Int
updatedAt: DateTime!
}
and a Root_Query, as depicted in Apollo devTools (See attached image), of:
query allPostsCommentsQuery {
allPostses {
id
displaysrc
caption
likes
comments {
id
posts {
id
}
text
user
}
}
}
Running Add_Comment_Mutation or Remove_Comment_MutationNew:
export const Add_Comment_Mutation = gql`
mutation createComment ($id: ID, $textVal: String!, $userVal: String!) {
createComments (postsId: $id, text: $textVal, user: $userVal){
id
text
user
}
}
`;
export const Remove_Comment_MutationNew = gql`
mutation removeComment ($cid: ID!) {
deleteComments(id: $cid) {
id
}
}
`;
does not correctly update reactive cache, and thus my UI does not correctly reflect any additions/deletions of comments, which are triggered by onClick events.
How do I get updateQueries to correctly work with this.props.client.mutate, as current attempt generates "Error: update(): expected target of $unshift to be an array; got undefined." errors (See below):
import { graphql, gql, withApollo } from 'react-apollo';
import ApolloClient from 'apollo-client';
import update from 'immutability-helper';
import { Add_Comment_Mutation, Remove_Comment_MutationNew } from '../graphql/mutations';
const Comments = React.createClass({
removeCommentMutation(commentID) {
console.log ("Remove_Comment_MutationNew is called for id=" + commentID);
const { client } = this.props;
return this.props.client.mutate({
mutation: Remove_Comment_MutationNew,
variables: {
"cid": commentID,
},
updateQueries: {
allPostsCommentsQuery: (previous, { mutationResult }) => {
console.log("Previous = " + previous);
const newComment = mutationResult.data.removeComment;
return update(previous, {
allPostses: {
comments: {
$set: [newComment],
},
},
});
}
}
})
.then(({ data }) => {
console.log('got data', data.deleteComments.id);
})
.catch(this.handleSubmitError);
},
Generated error:
Note - The issue appears to be with
const newComment = mutationResult.data.removeComment;
which is being returned as 'undefined', instead of as an object.
Error: update(): expected target of $unshift to be an array; got undefined.
at invariant (http://localhost:7770/static/bundle.js:23315:16)
at invariantPushAndUnshift (http://localhost:7770/static/bundle.js:71469:4)
at Object.$unshift (http://localhost:7770/static/bundle.js:71430:6)
at update (http://localhost:7770/static/bundle.js:71408:36)
at update (http://localhost:7770/static/bundle.js:71410:32)
at update (http://localhost:7770/static/bundle.js:71410:32)
at allPostsCommentsQuery (http://localhost:7770/static/bundle.js:54181:52)
at http://localhost:7770/static/bundle.js:39552:87
at tryFunctionOrLogError (http://localhost:7770/static/bundle.js:39457:17)
at http://localhost:7770/static/bundle.js:39552:44
at Array.forEach (native)
at data (http://localhost:7770/static/bundle.js:39536:47)
at apolloReducer (http://localhost:7770/static/bundle.js:39789:24)
at combination (http://localhost:7770/static/bundle.js:23011:30)
at computeNextEntry (<anonymous>:2:27051)
at recomputeStates (<anonymous>:2:27351)
at <anonymous>:2:30904
at Object.dispatch (http://localhost:7770/static/bundle.js:22434:23)
at dispatch (<anonymous>:2:31397)
at http://localhost:7770/static/bundle.js:41210:40
at http://localhost:7770/static/bundle.js:73223:17
at Object.dispatch (http://localhost:7770/static/bundle.js:23158:19)
at http://localhost:7770/static/bundle.js:40597:30
tryFunctionOrLogError # apollo.umd.js:1410
(anonymous) # apollo.umd.js:1501
data # apollo.umd.js:1485
apolloReducer # apollo.umd.js:1738
combination # combineReducers.js:132
computeNextEntry # VM77918:2
recomputeStates # VM77918:2
(anonymous) # VM77918:2
dispatch # createStore.js:179
dispatch # VM77918:2
(anonymous) # apollo.umd.js:3159
(anonymous) # index.js:14
dispatch # applyMiddleware.js:45
(anonymous) # apollo.umd.js:2546

I don't think you can concatenate mutations. At least the error message tells so. It should be something like:
...
removeCommentMutation(commentID) {
const { client } = this.props;
client.mutate({
mutatation: Remove_Comment_Mutation,
variables: {
"cid": commentID
},
updateQueries: {
removeComment: (previous, { mutationResult }) => {
const newComment = mutationResult.data.submitComment;
return update(prev, {
allPostses: {
comments: {
$unshift: [newComment],
},
},
});
}
}
});
}
...

Related

Cannot Get Apollo addItem Mutation to work on the client keep getting 400 error

All I want to do is add an item to the items array in my Cart object.
What I am trying to do is simply execute my backend addItem mutation. After that I want to manually update the cache, but for now I am just re-fetching the query because I am unable to even successfully get the query to run.
In this code I am using the pothos withinput plugin: link to docs
I have tried:
Just putting the hardcoded input object into the addItem hook
Listing each Variable out one by one into the addItem hook
Describing the types of each prop in the original gql MUTATION
And passing the hardcoded input into the addItem hook via variables object
Passing hardcoded values into the actual addItem mutation
I have tried inputting the proper typing via a gql tag example below:
const THE_TYPE = gql`input addItemInput {
cartId: String!
id: String!
name: String!
price: Float!
}
`
const MUTATION = gql`
mutation AddItem($input: ${THE_TYPE}!) {
addItem(input: $input){carts{
id
items{
name
}}}
`;
*When I run the following mutation in my graphiql interface it works:
mutation MyMutation{
addItem(input:{
cartId: "2",
id: "12",
name: "New Item!",
price: 1900,
}){
items{
name
}
}}
However when I run the mutation below I get a 400 error:
Error: Response not successful: Received status code 400
import { useQuery, gql, useMutation } from '#apollo/client';
export default function DisplayCarts() {
interface Cart {
id: string;
items: string[];
}
interface Items {
}
const GET_CARTS = gql`
query {
carts{
id
items{
name
}}} `;
const MUTATION = gql`
mutation AddItem($input: Any) {
addItem(input: $input){
carts{
id
items{
name
}}
}}`;
const { loading, error, data } = useQuery(GET_CARTS)
const [addItem] = useMutation(MUTATION, {
refetchQueries: [{ query: GET_CARTS }]
// update(cache, { data: { addItem } }) {
// addItem is the response of the query of add item function
// console.log(data);
// #ts-ignore
// const { carts } = cache.readQuery({ query: GET_CARTS });
// cache.writeQuery({
// query: GET_CARTS,
// data: { carts: [...carts, addItem] }
// })
// }
})
function AddTodo() {
let theInput = {
cartId: "2",
id: "12",
name: "New Item!",
price: 1900,
quantity: 2
}
// #ts-ignore
addItem({ variables: { input: theInput } });
};
Here is my backend resolver function using pothos
Keep in mind my query does work in my graphiql interface so the issue is probably not on the backend
builder.mutationType({
fields: (t) => ({
addItem: t.fieldWithInput({
input: {
cartId: t.input.string({ required: true }),
id: t.input.string({ required: true }),
name: t.input.string({ required: true }),
price: t.input.int({ required: true }),
quantity: t.input.int({ required: true, defaultValue: 1 }),
},
type: Cart,
resolve: (_, { input: { cartId, ...input } }) => {
const cart = CARTS.find((cart) => cart.id === cartId);
if (!cart) {
throw new Error(`Cart with id ${cartId} not found`)
}
return {
id: cartId,
items: [...cart?.items, input]
}
}
}),
}),
})
The problem lies with:
mutation AddItem($input: Any) {
addItem(input: $input){…}
There is no Any in GraphQL. The 400 is a result of an invalid query/mutation. Note that you're not actually running the same mutation that you are in GraphiQL.
Try using an input type for example in your typeDefs (on the server), add:
input addItemInput {
cartId: String!
id: String!
name: String!
price: Float!
}
Then in your client code:
const MUTATION = gql`
mutation AddItem($input: addItemInput) {
addItem(input: $input){…}
}
`
Firstly some necessary information:
When using pothos with input plugin it formulates the query type for you following the following rule: ${ParentType.name}${Field.name}Input. I hoghly recomend you follow the link and look at the docs yourself so you can understand exactly how your query should look.
Here is the link to the corresponding docs
The correct query:
const MUTATION = gql`
mutation AddItem($input:MutationAddItemInput!) {
addItem(input: $input){
items{
name
}
}
}
`;
If you get a 400 error it is probably your query is just wrong
If you get a weird error with in it check your brackets you might be missing one or two

GraphQLError: Syntax Error: Expected Name, found ":"

I am trying to Prisma along side with Apollo Server
and I keep getting this error
GraphQLError: Syntax Error: Expected Name, found ":"
this is the index.ts file
import { PrismaClient } from '#prisma/client';
import { ApolloServer } from 'apollo-server';
import { typeDefs } from './schema/schema';
import { Query } from './resolvers/Query';
import { Mutation } from './resolvers/Mutation';
const prisma = new PrismaClient();
const server = new ApolloServer({
typeDefs,
resolvers: {
Query,
Mutation,
},
context: {
prisma,
},
});
server.listen().then(({ url }: any) => {
console.log(`Server is running on ${url}`);
});
this is the schema.ts file
const { gql } = require('apollo-server');
export const typeDefs = gql`
type Query {
getProducts: [Product!]!
}
type Mutation {
addProduct(input: addProductInput): Boolean!
}
type Product {
name: String!
price: Float!
description: : String!
}
input addProductInput {
name: String!
price: Float!
description: : String!
}
`;
this is the Query.ts file in the resolvers folder
export const Query = {
getProducts: async (parent: any, args: any, { prisma }: any) => {
return await prisma.products.findMany();
},
};
this is the Query.ts file in the resolvers folder
export const Mutation = {
addProduct: async (parent: any, { input }: any, { prisma }: any) => {
const productData = {
name: input.name,
price: input.price,
description: input.description,
};
await prisma.products.create({
data: productData,
});
return true;
},
};
and lastly this is the Product model in schema.prisma file
model Product {
##map(name: "products")
id Int #id #default(autoincrement())
name String
price Float
description String
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
I have done some researches and all I got is that might be a missing bracket or a curly bracket, but I reviewed my code multiple times and did not find any mistakes.
In the schema definition, pay close attention to the Product type and addProductInput:
type Product {
name: String!
price: Float!
description: : String!
}
input addProductInput {
name: String!
price: Float!
description: : String!
}
`;
Are you sure the description fields should have two colons? I think they shouldn't have the middle one, and just be like description: String!

Are fields with list types forbidden in GraphQL schema stitching selection sets?

I have an array of entities that look like this:
const aEntities = [
{
id: 1,
name: 'Test',
oneToManyRelation: [
{
id: 2
},
{
id: 3
}
],
oneToOneRelation: {
id: 1
}
}
];
The entities are represented by the type AType. I want to make an extension of this type in a separate subschema and prove that it is possible to add fields that derive their values from the contents of oneToOneRelation and oneToManyRelation respectively.
The following schema, implementing a derived field based on oneToOneRelation, works fine:
const aSchema = makeExecutableSchema({
resolvers: {
Query: {
aEntities: () => aEntities
}
},
schemaTransforms: [stitchingDirectivesValidator],
typeDefs: gql`
${allStitchingDirectivesTypeDefs}
type AType {
id: ID!
name: String!
oneToOneRelation: AEmbeddedType!
}
type AEmbeddedType {
id: ID!
}
type Query {
aEntities: [AType!]!
}
`
});
const bSchema = makeExecutableSchema({
resolvers: {
AType: {
oneToOneId: ({ oneToOneRelation }) => oneToOneRelation.id
},
Query: {
aEntities_fromBSchema: (_, { keys }) => keys,
}
},
schemaTransforms: [stitchingDirectivesValidator],
typeDefs: gql`
${allStitchingDirectivesTypeDefs}
type AType #key(selectionSet: "{ oneToOneRelation { id } }") {
oneToOneId: String!
}
scalar Key
type Query {
aEntities_fromBSchema(keys: [Key!]!): [AType!]! #merge
}
`
})
const schema = stitchSchemas({
subschemaConfigTransforms: [stitchingDirectivesTransformer],
subschemas: [
{
schema: aSchema
},
{
schema: bSchema,
}
]
})
But once I add oneToManyRelation { id } to the selectionSet i run into problems:
const aSchema = makeExecutableSchema({
resolvers: {
Query: {
aEntities: () => aEntities
}
},
schemaTransforms: [stitchingDirectivesValidator],
typeDefs: gql`
${allStitchingDirectivesTypeDefs}
type AType {
id: ID!
name: String!
oneToManyRelation: [AEmbeddedType!]!
oneToOneRelation: AEmbeddedType!
}
type AEmbeddedType {
id: ID!
}
type Query {
aEntities: [AType!]!
}
`
});
const bSchema = makeExecutableSchema({
resolvers: {
AType: {
oneToManyIds: ({ oneToManyRelation }) => oneToManyRelation.map(({ id }) => id),
oneToOneId: ({ oneToOneRelation }) => oneToOneRelation.id
},
Query: {
aEntities_fromBSchema: (_, { keys }) => keys,
}
},
schemaTransforms: [stitchingDirectivesValidator],
typeDefs: gql`
${allStitchingDirectivesTypeDefs}
type AType #key(selectionSet: "{ oneToOneRelation { id }, oneToManyRelation { id } }") {
oneToOneId: String!
oneToManyIds: [String!]!
}
scalar Key
type Query {
aEntities_fromBSchema(keys: [Key!]!): [AType!]! #merge
}
`
})
I get the following error:
oneToManyRelation.map is not a function
And when I log the keys parameter in the aEntities_fromBSchema resolver it seems that oneToManyRelation haven't been resolved to be an array at all, but rather an (empty) object:
[
{
oneToOneRelation: [Object: null prototype] { id: '1' },
oneToManyRelation: [Object: null prototype] { id: undefined },
__typename: 'AType'
}
]
Is referencing list types in key selection sets simply forbidden as of graphql-tools v 7.0.2? It looks like I actually can circumvent the issue by using a subschema merge config defined outside of the SDL (without batching, instead using the args and selectionSet config parameters), but for validation/gateway reasons I'd prefer to have all my subschemas contain all of their type merging instructions as SDL directives.
Nb. This is a simplified representation of a real world problem.
Nb2. In the real world application one of my subschemas is a remote GraphQL application that I don't control, hence the need for some advanced tailoring in the stitching layer.
Edit: Simply adding the following to the merge options on the subschema config seems to solve the problem. Someone know of a good reason why this doesn't seem to be reproducible with SDL directives? (Or a good way to do so?)
// AType
{
argsFromKeys: (keys) => ({ keys }),
fieldName: 'aEntities_fromBSchema',
key: ({ oneToOneRelation, oneToManyRelation }) => ({ oneToManyRelation, oneToOneRelation }),
selectionSet: '{ oneToOneRelation { id }, oneToManyRelation { id } }'
}
You have likely found a bug! Please open an issue on the GitHub repo so we can track it. :)

apollo-server-express: how to resolve inside subscriptions?

When doing regular graphQL queries, I have no trouble to get nested objects or computations. But when inside a subscription like:
subscription {
Event(filter: {
mutation_in: [CREATED]
}) {
node {
title
description
start
end
seats
bookings
owner {
email
}
}
}
}
It breaks as soon as I add bookings or owner { ... }. I see no error message on then server. QraphiQL only displays a [object Object] as a response.
The interesing par of the schema is:
type Event {
id: ID!
title: String!
description: String!
owner: User!
seats: Int
bookings: Int
start: DateTime!
end: DateTime!
tickets: [EventTicket!]!
}
type Mutation {
createEvent(event: EventInput!): Event
}
input EventInput {
title: String!
description: String!
seats: Int
start: DateTime!
end: DateTime!
}
type Subscription {
Event(filter: EventSubscriptionFilter): EventSubscriptionPayload
}
input EventSubscriptionFilter {
mutation_in: [_ModelMutationType!]
}
type EventSubscriptionPayload {
mutation: _ModelMutationType!
node: Event
}
enum _ModelMutationType {
CREATED
UPDATED
DELETED
}
And the resolvers:
import { ObjectId } from "mongodb"
import pubsub from "../../utils/pubsub"
export default {
// ...
Mutation: {
createEvent: async (_, data, { mongo: { Events }, user }) => {
const newEvent = data.event
newEvent.ownerId = user._id
const response = await Events.insert(newEvent)
const [_id] = response.insertedIds
newEvent._id = _id
pubsub.publish("Event", { Event: { mutation: "CREATED", node: newEvent } })
return newEvent
},
},
Subscription: {
Event: {
subscribe: () => pubsub.asyncIterator("Event"),
},
},
Event: {
id: event => event._id.toString(),
owner: async (event, _, { mongo: { Users } }) => Users.findOne({ _id: event.ownerId }),
bookings: async (event, _, { mongo: { EventTickets } }) =>
EventTickets.find({ eventId: event._id }).count(),
tickets: async (event, _, { mongo: { EventTickets } }) =>
EventTickets.find({ eventId: event._id }).toArray(),
},
}
Any idea about how to deal with this (and/or how to get a proper error message to debug ^^).
Ok after searching, I found the solution/hack here.
SubscriptionServer.create(
{
execute, subscribe, schema,
onOperation: (message, params, webSocket) => {
return { ...params, context: {mongo} }
},
},
{ server, path: '/subscriptions' },
);
Thanks nharraud.

How to pass the values of a connection to a subscription?

I'm trying to return a subscription which consists of the following:
const postsSubscription = gql`
subscription postAdded {
postAdded {
id
title
description
author{
name
}
}
}
`
What happens is that Author is type of User, and I just pass an authorId. That means that I don't have the author name when I create the Post:
createPost: async (root, req, { posts }) => {
const Item = {
id: uuid.v4(),
authorId: '565dbdc0-36f2-4bba-be67-c126d0c71fff',
...req
}
await posts.create({ Item })
pubsub.publish('postAdded', { postAdded: Item })
return Item
},
Here is the Author resolver:
Post: {
author: async({ authorId }, req, { users }) => {
const Key = { id: authorId }
const { Item } = await users.get({ Key })
return Item
}
}
Here is the schema:
type Post {
id: ID
title: String
description: String
author: User #relation(name: "PostAuthor")
}
type User {
id: ID
name: String
email: String
password: String
posts: [Post] #relation(name: "UserPosts")
}
type PostPayload {
post: Post
}
type CreateUserPayload {
user: User
}
type Query {
allPosts: [Post]
allUsers: [User]
post(id: ID!): Post
user(id: ID!): User
}
type Mutation {
createPost(input: CreatePostInput!): PostPayload
updatePost(input : UpdatePostInput!): PostPayload
createUser(input : CreateUserInput!): CreateUserPayload
}
type Subscription {
postAdded: Post
}
input CreatePostInput {
title: String!
description: String!
}
input UpdatePostInput {
id: ID!
title: String!,
description: String!
}
input CreateUserInput {
name: String!
email: String!
password: String!
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
So, my question is, how to pass all required fields (including the connections) to the subscription?
I made it work, but not as I wanted.
1) I had to remove this part of the code:
Post: {
author: async({ authorId }, req, { users }) => {
const Key = { id: authorId }
const { Item } = await users.get({ Key })
return Item
}
}
and added this function to the createPost function itself:
createPost: async (root, { input }, { posts, users }) => {
const Key = { id: '3b1884b8-9ee7-4d9d-ab2f-ff32bcd69b9a' }
const user = await users.get({ Key })
const Item = {
id: uuid.v4(),
author: user.Item,
...input
}
await posts.create({ Item })
await pubsub.publish(POST_ADDED_TOPIC, { [POST_ADDED_TOPIC]: Item })
return { post: Item }
}
So this is kinda fixed. But, if you know how to fix this, using the first approach (Post: author thingy) I'll appreciate.

Resources