I'm building an event managing database that handles RSVPs and links those RSVPs to a specific event. I'm using React, Apollo, GraphQL and Prisma as my stack and am having trouble correctly writing a mutation that would link an RSVP to an existing event. Still relatively new to the syntax, but I can't grab the event.id when I'm using the createRsvp mutation.
I'm able to pass the event.id down on the front-end through props, and I think a less-elegant way of receiving event.id in the args of the RSVP would be creating a hidden form with the event.id, but I KNOW there's got to be a way through graphQL. I've tried looking through the docs and various examples on grabbing the id from a different object. Any help is much appreciated :)
On the backend, here's my datamodel:
type Rsvp {
id: ID! #id
event: Event! #relation(link: INLINE)
firstName: String!
lastName: String!
email: String! #unique
company: String
jobTitle: String
mobile: String
dietary: String
}
type Event {
id: ID! #id
isPublished: Boolean
title: String!
startDate: String!
endDate: String!
description: String!
image: String
address: String
url: String!
suburb: String
postcode: String
state: String
country: String
guestLimit: Int
rsvps: [Rsvp]
}
The actual resolver for the Mutation.. I think I'm incorrectly retrieving the event.id here. My though process is that data: {...args} is first taking in the data from the RSVP form, and the connection to event is linking it to a certain event.
async createRsvp(parent, args, ctx, info) {
// 1. Query the event that is being registered for
const eventId = ctx.request.event.id;
// 2. Create the RSVP for this specific event
const rsvp = await ctx.db.mutation.createRsvp(
{
data: {
...args,
event: {
connect: { id: eventId }
}
}
},
info
);
console.log(rsvp);
return rsvp;
}
};
On the front end, this is what my Mutation looks like
const RSVP_MUTATION = gql`
mutation RSVP_MUTATION(
$email: String!
$firstName: String!
$lastName: String!
$company: String
$jobTitle: String
$mobile: String
$dietary: String
) {
createRsvp(
email: $email
firstName: $firstName
lastName: $lastName
company: $company
jobTitle: $jobTitle
mobile: $mobile
dietary: $dietary
) {
id
}
}
`;
and finally, the Mutation function in the form:
<Mutation mutation={RSVP_MUTATION} variables={({ id }, this.state)}>
{(createRsvp, { loading, error }) => (
<Form
onSubmit={async e => {
e.preventDefault();
const res = await createRsvp();
}}>
The error I receive in the console is "Uncaught (in promise) Error: GraphQL error: Cannot read property 'id' of undefined" which leads me to believe I'm incorrectly trying to access the event.id. Any tips or advice? Thanks again!
Divide and conquer
You can use /graphiql playground to test queries and mutations (API generally).
Testing this mutation:
mutation RSVP_MUTATION(
$email: String!
$firstName: String!
$lastName: String!
$company: String
$jobTitle: String
$mobile: String
$dietary: String
) {
createRsvp(
email: $email
firstName: $firstName
lastName: $lastName
company: $company
jobTitle: $jobTitle
mobile: $mobile
dietary: $dietary
) {
id
}
}
... with required variables you should quickly notice that you need to pass event id as variable, too. No special (more/less elegant), separate method required, this is a standard, query/mutation and variables, nothing more!!!
Just add eventId variable:
mutation RSVP_MUTATION(
$eventId: ID!
$email: String!
$firstName: String!
$lastName: String!
$company: String
$jobTitle: String
$mobile: String
$dietary: String
) {
createRsvp(
eventId: $eventId
email: $email
firstName: $firstName
lastName: $lastName
company: $company
jobTitle: $jobTitle
mobile: $mobile
dietary: $dietary
) {
id
}
}
Of course in resolver you'll get it within args, you can use f.e.
const { eventId, ...rest } = args;
const rsvp = await ctx.db.mutation.createRsvp(
{
data: {
...rest,
event: {
React variables can be passed f.e. by
variables={{ eventId: id, ...this.state }}
Related
I am wondering if it is possible to pass multiple IDs to a useQuery for apollo hook. Or run a single query per ID, and if so how would I go about doing so.
I have the following query
const DECOR_SHEET = gql`
query GetDecorSheet($id: ID!) {
findDecorSheetByID(id:$id){
refIdArray
sheetName
_id
}
}
`;
and the following useQuery hook
const { loading, error, data: sheetData } = useQuery(DECOR_SHEET, {
variables: { id: id },
context: {
headers: {
authorization: cookieBearer,
},
},
});
I have the following IDs 293164663883956749, 293526016787218952 and I would like to return into one object in order to render into a component.
I am using Fauna DB and this is the input graphQL schema
type Catalog {
decor: Boolean
clothing: Boolean
supplies: Boolean
furniture: Boolean
owner: User
}
type Decor {
description: String
pieces: Int
purchaser: String
alterations: Boolean
cost: Int
purchaseDate: Date
category: String
image: String
itemNum: Int
owner: User!
visible: Boolean
}
type DecorSheet {
sheetName: String
refIdArray: String
owner: User!
}
type User {
email: String! #unique
catalog: Catalog
decor: [Decor!] #relation
decorSheet: [DecorSheet!] #relation
}
and this is the generated schema
directive #embedded on OBJECT
directive #collection(name: String!) on OBJECT
directive #index(name: String!) on FIELD_DEFINITION
directive #resolver(
name: String
paginated: Boolean! = false
) on FIELD_DEFINITION
directive #relation(name: String) on FIELD_DEFINITION
directive #unique(index: String) on FIELD_DEFINITION
type Catalog {
_id: ID!
decor: Boolean
clothing: Boolean
supplies: Boolean
owner: User
furniture: Boolean
_ts: Long!
}
input CatalogInput {
decor: Boolean
clothing: Boolean
supplies: Boolean
furniture: Boolean
owner: CatalogOwnerRelation
}
input CatalogOwnerRelation {
create: UserInput
connect: ID
disconnect: Boolean
}
scalar Date
type Decor {
purchaseDate: Date
visible: Boolean
image: String
description: String
_id: ID!
alterations: Boolean
cost: Int
pieces: Int
category: String
owner: User!
purchaser: String
itemNum: Int
_ts: Long!
}
input DecorInput {
description: String
pieces: Int
purchaser: String
alterations: Boolean
cost: Int
purchaseDate: Date
category: String
image: String
itemNum: Int
owner: DecorOwnerRelation
visible: Boolean
}
input DecorOwnerRelation {
create: UserInput
connect: ID
}
type DecorPage {
data: [Decor]!
after: String
before: String
}
type DecorSheet {
refIdArray: String
_id: ID!
sheetName: String
owner: User!
_ts: Long!
}
input DecorSheetInput {
sheetName: String
refIdArray: String
owner: DecorSheetOwnerRelation
}
input DecorSheetOwnerRelation {
create: UserInput
connect: ID
}
type DecorSheetPage {
data: [DecorSheet]!
after: String
before: String
}
scalar Long
type Mutation {
updateUser(
id: ID!
data: UserInput!
): User
createUser(data: UserInput!): User!
createDecorSheet(data: DecorSheetInput!): DecorSheet!
createDecor(data: DecorInput!): Decor!
deleteCatalog(id: ID!): Catalog
updateCatalog(
id: ID!
data: CatalogInput!
): Catalog
updateDecor(
id: ID!
data: DecorInput!
): Decor
updateDecorSheet(
id: ID!
data: DecorSheetInput!
): DecorSheet
deleteDecor(id: ID!): Decor
deleteUser(id: ID!): User
createCatalog(data: CatalogInput!): Catalog!
deleteDecorSheet(id: ID!): DecorSheet
}
type Query {
findUserByID(id: ID!): User
findCatalogByID(id: ID!): Catalog
findDecorByID(id: ID!): Decor
findDecorSheetByID(id: ID!): DecorSheet
}
scalar Time
type User {
catalog: Catalog
email: String!
_id: ID!
decor(
_size: Int
_cursor: String
): DecorPage!
decorSheet(
_size: Int
_cursor: String
): DecorSheetPage!
_ts: Long!
}
input UserCatalogRelation {
create: CatalogInput
connect: ID
disconnect: Boolean
}
input UserDecorRelation {
create: [DecorInput]
connect: [ID]
disconnect: [ID]
}
input UserDecorSheetRelation {
create: [DecorSheetInput]
connect: [ID]
disconnect: [ID]
}
input UserInput {
email: String!
catalog: UserCatalogRelation
decor: UserDecorRelation
decorSheet: UserDecorSheetRelation
}
There is an option to query with Fauna's FQL which may have a way of querying multiple IDs I will have to look into that, but would prefer to do this in graphQL with apollo if possible.
Thanks ahead of time
Thanks to support from FaunaDB
The following query and UDF does the trick
Query
type Query {
getMultipleDecors(DecorId: [ID!]): [Decor]
#resolver(name: "get_multiple_decors")
}
udf named get_multiple_decors
Query(
Lambda(
["input"],
Let(
{
data: Map(
Var("input"),
Lambda("x", Get(Ref(Collection("Decor"), Var("x"))))
)
},
Var("data")
)
)
)
If it's always exactly two ids, you can fetch both objects in a single query easily using field aliases:
const DECOR_SHEET = gql`
query GetDecorSheet($firstId: ID!, $secondId: ID!) {
firstDecorSheet: findDecorSheetByID(id: $firstId) {
refIdArray
sheetName
_id
}
secondDecorSheet: findDecorSheetByID(id: $secondId) {
refIdArray
sheetName
_id
}
}
`;
I have a backend made with Express / Apollo Server / GraphQL / MongoDB / Mongoose and a frontend made with React.
I have my component where I want to do mutations and queries, I checked the connection between frontend and backend and it's good.
My mutation on backend:
createUser(input: SignupInput!): AuthUser!
input SignupInput {
name: String!
username: String!
email: String!
password: String!
}
The AuthUser return is an User! that has more fields but I think it's not necessary info.
On my frontend
const NEW_USER = gql`
mutation CreateUser($type: SignupInput!) {
createUser(input: $type) {
user {
id
}
}
}
`;
const [newTest, { data }] = useMutation(NEW_USER);
const onSubmit = (formData) => {
newTest({
variables: {
name: formData.name,
username: formData.username,
email: formData.email,
password: formData.password,
},
});
};
The network response for error 400:
"Variable \"$type\" of required type \"SignupInput!\" was not provided."
I don't really understand why this is happening, I've seen in other people's code that creating the variable with the name of the input type you have on the backend works.
If I try to destructure the SignInput! type and write {$name: !String, ...) I get this error:
Unknown argument \"name\" on field \"Mutation.createUser\".", locations: […], extensions: {…} }
newTest({
variables: {
input: {
name: formData.name,
username: formData.username,
email: formData.email,
password: formData.password,
}
},
});
Input was not passed in the mutation function.
I have a React client-side project and a Node.js/GraphQL api in two separate repo's.
In my React app, I want to pass an object as variable type into my mutation. Here's how my mutation looks like:
export const CREATE_SPEAKER = gql`
input Expertise {
title: String!
domain: String!
}
mutation CreateSpeaker(
$name: String!
$age: String!
$nationality: String!
$avatar: String!
$expertise: Expertise!
) {
createSpeaker(
speakerInput: {
name: $name
age: $age
nationality: $nationality
avatar: $avatar
expertise: $expertise
}
) {
name
age
nationality
avatar
expertise {
title
domain
}
}
}
`;
In my Node.js project I have the following schema:
input SpeakerInput {
name: String!
age: String!
expertise: ExpertiseInput!
nationality: String!
avatar: String
}
input ExpertiseInput {
title: String!
domain: String!
}
And my resolver:
createSpeaker: async args => {
const { name, age, nationality, avatar, expertise } = args.speakerInput;
const newSpeaker = new Speaker({
name,
age,
nationality,
avatar,
expertise: {
title: expertise.title,
domain: expertise.domain
}
});
try {
return await newSpeaker.save();
} catch (error) {
throw ("Failed to create speaker:: ", error);
}
}
But I'm getting the following error when trying to create the speaker:
Uncaught (in promise) Invariant Violation: Schema type definitions not
allowed in queries. Found: "InputObjectTypeDefinition"
Any suggestions/ideas how to do this?
You can't define additional types when sending requests to a GraphQL service and you don't need to -- just use the types you've already defined on the server (in this case ExpertiseInput:
$expertise: ExpertiseInput!
However, there's no need to use this many variables in the first place:
mutation CreateSpeaker($input: SpeakerInput!) {
createSpeaker(speakerInput: $input) {
name
age
nationality
avatar
expertise {
title
domain
}
}
}
I Want to syntax highlight the code inside typeDef. It is possible?
There is a extension for this? Or I have to code the typeDef other way?
export const typeDef = `
type User {
_id: ID!
email: String!
password: String
createdEvents: [Event!]
}
type AuthData {
userId: ID!
token: String!
tokenExpiration: Int!
}
input UserInput {
email: String!
password: String!
}
`;
Use String.raw to trick VSCode into Syntax Highlighting GraphQL. It works for other languages as well.
export const gql = String.raw
export const typeDef = gql`
type User {
_id: ID!
email: String!
password: String
createdEvents: [Event!]
}
type AuthData {
userId: ID!
token: String!
tokenExpiration: Int!
}
input UserInput {
email: String!
password: String!
}
`
Assuming you're using the right extension, you need to use the gql tag from graphql-tag.
const gql = require('graphql-tag')
const typeDefs = gql`
type User { ... }
`
The tag parses the provided string and returns a DocumentNode object, which is what should be passed to makeExecutableSchema or the ApolloServer constructor. On the client side, the queries used by ApolloClient are also expected to be DocumentNode objects and should be wrapped the same way.
The extension is able to detect usage of the tag and apply syntax highlighting accordingly.
So I have the following in my Prisma datamodel. There's a relationship between the games and the characters, where the characters can appear in multiple games. Also the characters should be able to be listed on the games when the games alone are queried:
type Game {
id: ID! #unique
name: String! #unique
name_ja: String #unique
name_ko: String #unique
name_zh_CN: String #unique
name_zh_TW: String #unique
name_zh_HK: String #unique
characters: [Character]
filters: [GameFilter]
}
type Character {
id: ID! #unique
name: String! #unique
name_ja: String #unique
name_ko: String #unique
name_zh_CN: String #unique
name_zh_TW: String #unique
name_zh_HK: String #unique
games: [Game]
}
I then have this as a mutation for updating characters in my schema. I pass along an array of Game IDs because there's the possibility of multiple games being added within the same mutation:
updateCharacter(
name: String
name_ja: String
name_ko: String
name_zh_CN: String
name_zh_HK: String
name_zh_TW: String
id: ID!
games: [ID]
): Character!
And the following mutation written:
async updateCharacter(parent, args, ctx, info) {
if (!ctx.request.userId) {
throw new Error('You must be logged in');
}
const updates = {...args};
delete updates.id;
delete updates.games;
console.log(args.games);
const res = await ctx.db.mutation.updateCharacter({
data: updates,
games: {
connect: {
id: args.games
}
},
where: {
id: args.id
}
}, info);
return res;
}
And the following mutation written React-side. It's meant to pass along an array of IDs to the mutation (which will be expected because the schema assumes that an array of IDs will be passed along:
const UPDATE_CHARACTER_MUTATION = gql`
mutation UPDATE_CHARACTER_MUTATION(
$name: String!
$name_ja: String!
$name_ko: String!
$name_zh_CN: String!
$name_zh_TW: String!
$name_zh_HK: String!
$id: ID!
$games: [ID]!
) {
updateCharacter(
name: $name
name_ja: $name_ja
name_ko: $name_ko
name_zh_CN: $name_zh_CN
name_zh_TW: $name_zh_TW
name_zh_HK: $name_zh_HK
games: $games
id: $id
) {
id
name
name_ja
name_ko
name_zh_CN
name_zh_TW
name_zh_HK
games {
id
name
}
}
}
`;
I am also passing along the IDs of the games in an array onSubmit within the form to the mutation.
The result is that the mutation passes, but will only update the name fields, and the connection with the Games remains unchanged.
Not sure what I can do to make this mutate properly. I'm not sure whether passing the array directly into the mutation in the connect is what's causing the issue, but I tried mapping through and passing along each game id individually, but had the same result of the connection not updating.
The problem seems to be two-fold.
Regarding not seeing any changes at all:
In the mutation written, games is added to the async request json outside of data, meaning the data will never be found by the request.
Rather than:
data: ...,
games: ...
games must be part of data like such:
data: {
games: ...
}
Regarding wanting to do multiple connects:
GraphQL supports this style of nested updates using the following connect syntax:
connect: [
{ id : /* id from args */ },
{ id : /* another id from args */ },
...
]