GraphQL (Prisma) Mutation data undefined - reactjs

I’m trying to do a mutation and I keep getting an error about data being null on the React side. However, if I try the same mutation in the GraphQL console it works. Also, I know the endpoint is working because I can Query data with no problem.
Everything
Server code (resolver):
async signup(parent, args, ctx, info) {
// lowercase their email
args.email = args.email.toLowerCase();
// hash their password
const password = await bcrypt.hash(args.password, 10);
// create the user in the database
const user = await ctx.db.mutation.createUser({
data: {
...args,
password,
}
}, info);
return user;
}
Mutation (React Side)
mutation signupUser($email: String!, $password: String!, $name: String!) {
signup(email: $email, password: $password, name: $name) {
__typename
id
email
password
name
}
}
TypeError: Cannot read property 'data' of undefined
at Mutation._this.onMutationCompleted (react-apollo.esm.js:477)
at react-apollo.esm.js:434
Also here is a snippet of my Mutation on the component
<Mutation
mutation={signUpUserMutation}
onCompleted={(user) => {
handleClose();
}}
onError={(error) => {
console.log(error)
setOpen(true);
}}
>
{signup => (
<Form
onSubmit={async (values, { setSubmitting }) => {
await signup({
variables: {
name: values.name,
email: values.email,
password: values.password,
},
});
setSubmitting(false);
}}
>
{({
values, errors, handleChange, handleBlur, isSubmitting,
}) => (

In your schema
type User {
id: Int
name: String
email: String
password: String
}
type Response {
status: Boolean
message: String
data: User
}
type Mutation {
signUp(name: String!, email: String!, password: String!) : Response
}
On Mutation resolver signUp function
async signUp(parent, args, { db }, info) {
// lowercase their email
args.email = args.email.toLowerCase();
// hash their password
const password = await bcrypt.hash(args.password, 10);
// create the user in the database
const data = {
...args,
password,
}
const user = await db.mutation.createUser(data, info);
return user;
}
in database mutation createUser function you can access like that
const createUser = async ({ name, email, password }, info) => {
try {
// Code to save user information in database
return { status: true,
message: "User registration successful. You can login now.",
data: createdUser
}
} catch(e) {
return { status: false, message: e.message, data: null }
}
}
And your query
mutation signupUser($email: String!, $password: String!, $name: String!) {
signup(email: $email, password: $password, name: $name) {
status
message
data {
id
email
password
name
}
}
}

Related

Can't get my reactive variable from graphql local query

I'm trying to setup a Apollo 3 local state with react. For an unknown reason my query does not work when I try to read a custom type (isAuthenticated query works well). Can you please help me to understand I literaly explored all the documentation and the link on internet about local state management.
Thank you
cache.js
import { InMemoryCache, makeVar, gql } from '#apollo/client'
export const isAuthenticatedVar = makeVar(!!localStorage.getItem('token'))
export const userVar = makeVar("")
export const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
isAuthenticated: {
read() {
return isAuthenticatedVar()
}
},
user: {
read() {
return userVar()
},
__typename: 'User',
}
}
}
}
})
export const IS_AUTHENTICATED = gql`
query IS_AUTHENTICATED {
isAuthenticated #client
}
`
export const GET_LOCAL_USER = gql`
query GET_LOCAL_USER {
user #client
}
`
typedej.js
import { gql } from '#apollo/client'
export const typeDef = gql`
extend type User {
id: ID!
name: String!
surname: String!
email: String!
password: String!
phone: String!
adress: Adress!
isAdmin: Boolean!
isCoach: Boolean!
isManager: Boolean!
}
extend type Query {
isAuthenticated: Boolean!
user: User!
}
`
export default typeDef
test.js
const login = (email, password) => {
const emailLower = email.toLowerCase()
client.query({
query: LOGIN,
variables: { email: emailLower, password: password }
})
.then(result => {
console.log(result)
localStorage.setItem('token', result.data.login.token)
localStorage.setItem('userId', result.data.login.user.id)
isAuthenticatedVar(true)
userVar({ ...result.data.login.user})
console.log('USER VAR SETUP')
///Console.log show that userVar contains an object with all User data
console.log(userVar())
var test
try{
//test return NULL object
test = client.readQuery({ query: GET_LOCAL_USER}).user
}catch(error) {
console.log(error)
}
console.log('FETCH QUERY userVar')
console.log(test)
navigate("/")
})
.catch(error => {
console.log(error)
})
}
Tried several ways but can't find how to resolve this. I don't understand why documentation don't explain how to use with Object Type

PubSub does not work in Client page but works in Graphql playground - TypeError: data.getPosts is not iterable

I am trying to make a Social app using MongoDB, Express, React, Node, Graphql with Apollo , I am following a video from freecodecamp : Link to the video
I am using pubsub to achieve a realtime functionality, like when ever a new post has been added it should show up in the home page.
I might have missed the code of some file that's why
I am dropping a link to my github repo containing the whole project : Link to the github repo
Home.js:
import React, { useContext } from 'react';
import { useQuery } from '#apollo/react-hooks';
import { Grid, Transition } from 'semantic-ui-react';
import { AuthContext } from '../context/auth';
import PostCard from '../components/PostCard';
import PostForm from '../components/PostForm';
import { FETCH_POSTS_QUERY } from '../util/graphql';
function Home() {
const { user } = useContext(AuthContext);
const { data, loading, error } = useQuery(FETCH_POSTS_QUERY);
if(data) {
const { posts } = data || [];
return (
<Grid columns={3}>
<Grid.Row className="page-title">
<h1>Recent Posts</h1>
</Grid.Row>
<Grid.Row>
{user && (
<Grid.Column>
<PostForm />
</Grid.Column>
)}
{loading && <h1>Loading posts..</h1>}
{data && (
<Transition.Group>
{posts &&
posts.map((post) => (
<Grid.Column key={post.id} style={{ marginBottom: 20 }}>
<PostCard post={post} />
</Grid.Column>
))}
</Transition.Group>
)}
</Grid.Row>
</Grid>
);
}
if(error) {
return error.message;
}
}
export default Home;
I render all the posts in Home.js
Resolvers => posts.js :
const { AuthenticationError, UserInputError } = require('apollo-server');
const Post = require('../../models/Post');
const checkAuth = require('../../util/check-auth');
module.exports = {
Query: {
async getPosts() {
try {
const posts = await Post.find().sort({ createdAt: -1 });
return posts;
} catch (err) {
throw new Error(err);
}
},
async getPost(_, { postId }) {
try {
const post = await Post.findById(postId);
if (post) {
return post;
} else {
throw new Error('Post not found');
}
} catch (err) {
throw new Error(err);
}
}
},
Mutation: {
async createPost(_, { body }, context) {
const user = checkAuth(context);
if (body.trim() === '') {
throw new Error('Post body must not be empty');
}
const newPost = new Post({
body,
user: user.id,
username: user.username,
createdAt: new Date().toISOString()
});
const post = await newPost.save();
context.pubsub.publish('NEW_POST', {
newPost: post,
}).then(()=>{
console.log("working")
});
return post;
},
async deletePost(_, { postId }, context) {
const user = checkAuth(context);
try {
const post = await Post.findById(postId);
if (user.username === post.username) {
await post.delete();
return 'Post deleted successfully';
} else {
throw new AuthenticationError('Action not allowed');
}
} catch (err) {
throw new Error(err);
}
},
async likePost(_, { postId }, context) {
const { username } = checkAuth(context);
const post = await Post.findById(postId);
if (post) {
if (post.likes.find((like) => like.username === username)) {
// Post already likes, unlike it
post.likes = post.likes.filter((like) => like.username !== username);
} else {
// Not liked, like post
post.likes.push({
username,
createdAt: new Date().toISOString()
});
}
await post.save();
return post;
} else throw new UserInputError('Post not found');
}
},
Subscription: {
newPost: {
subscribe: (_, __, { pubsub }) => pubsub.asyncIterator('NEW_POST')
}
}
};
Resolvers => index.js :
const postsResolvers = require('./posts');
const usersResolvers = require('./users');
const commentsResolvers = require('./comments');
module.exports = {
Post: {
likeCount: (parent) => parent.likes.length,
commentCount: (parent) => parent.comments.length
},
Query: {
...postsResolvers.Query
},
Mutation: {
...usersResolvers.Mutation,
...postsResolvers.Mutation,
...commentsResolvers.Mutation
},
Subscription: {
...postsResolvers.Subscription
}
};
tyDefs.js :
const { gql } = require('apollo-server');
module.exports = gql`
type Post {
id: ID!
body: String!
createdAt: String!
username: String!
comments: [Comment]!
likes: [Like]!
likeCount: Int!
commentCount: Int!
}
type Comment {
id: ID!
createdAt: String!
username: String!
body: String!
}
type Like {
id: ID!
createdAt: String!
username: String!
}
type User {
id: ID!
email: String!
token: String!
username: String!
createdAt: String!
}
input RegisterInput {
username: String!
password: String!
confirmPassword: String!
email: String!
}
type Query {
getPosts: [Post]
getPost(postId: ID!): Post
}
type Mutation {
register(registerInput: RegisterInput): User!
login(username: String!, password: String!): User!
createPost(body: String!): Post!
deletePost(postId: ID!): String!
createComment(postId: String!, body: String!): Post!
deleteComment(postId: ID!, commentId: ID!): Post!
likePost(postId: ID!): Post!
}
type Subscription {
newPost(
id: ID!,
body: String!,
createdAt: String!,
username: String!,
likeCount: Int!,
commentCount: Int!): Post!
}
`;
This is the index.js file of my server (backend) :
const { ApolloServer, PubSub } = require('apollo-server');
const mongoose = require('mongoose');
const typeDefs = require('./graphql/typeDefs');
const resolvers = require('./graphql/resolvers');
const { MONGODB } = require('./config.js');
const pubsub = new PubSub();
const PORT = process.env.port || 5000;
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => ({ req, pubsub })
});
mongoose
.connect(MONGODB, { useUnifiedTopology: true , useNewUrlParser: true })
.then(() => {
console.log('MongoDB Connected');
return server.listen({ port: PORT });
})
.then((res) => {
console.log(`Server running at ${res.url}`);
})
.catch(err => {
console.error(err)
})
I am intialising the pubsub in the index.js file only,
This is the error I am getting :
TypeError: data.getPosts is not iterable 0.chunk.js:124321
Screen shot of the error :
This error is one problem the other is that whenever I click submit the same error occurs, but the post appears when I run the subscription in Graphql Playground not in the client page but when I refresh the page the post appears there.
In case you're using Apollo v3, and you're having some issues with caching while adding posts:
const [createPost, { error }] = useMutation(CREATE_POST_MUTATION, {
variables: values,
update(proxy, result) {
const data = proxy.readQuery({
query: FETCH_POSTS_QUERY,
});
proxy.writeQuery({
query: FETCH_POSTS_QUERY,
data: {
getPosts: [result.data.createPost, ...data.getPosts],
},
});
values.body = "";
},
onError(err) {
<== also add this so the page doesn't break
return err;
},
});

Graphql mutation throwing invalid value error

I'm trying to make a mutation request to my graphql api but I'm getting an invalid value error. I'm using graphql codegen with typescript and react on my front end. I directly copied my working graphql mutation that I created on graphql playground. Codegen isn't giving any errors when I generate, typescript isn't giving me any errors and I have checked my syntax like 10 times but I can't for the life of me figure out why it's going wrong.
error code: GRAPHQL_VALIDATION_FAILED
error message: "Argument "data" has invalid value {email: $email, username: $username, password: $password, confirmPassword: $confirmPassword}."
My backend resolver looks like this
#Mutation(() => LoginResponse)
async signupLocal(
#Arg("data") signupLocalInput: SignupLocalInput,
#Ctx() { res }: MyContext
): Promise<LoginResponse> {
const user = await signupLocalService(signupLocalInput);
sendRefreshToken(res, createRefreshToken(user));
return {
accessToken: createAccessToken(user),
user,
};
}
My frontend graphql mutation looks like this
mutation SignupLocal(
$email: String!
$username: String!
$password: String!
$confirmPassword: String!
) {
signupLocal(
data: {
email: $email
username: $username
password: $password
confirmPassword: $confirmPassword
}
) {
accessToken
}
}
And my implementation looks like this
const [signupLocal] = useSignupLocalMutation();
const onSubmit = async (data: FormData) => {
console.log(data);
const { email, username, password, confirmPassword } = data;
const response = await signupLocal({
variables: { email, username, password, confirmPassword },
});
console.log(response);
};

useMutation result returning weird structure/ not returning requested fields

What I am trying to do: using useMutation to submit an array of array to mongoDB.
What happened: it did successfully save in mongoDb and in the correct format BUT the 'result.data' of useMutation is not as expected, it's too weired to be used.
This is my array of array (of course, it started as an empty array and was pushed in as user adds attraction to the days in their itinerary).
let itinerary = [{placeIds: ["ChIJafBhcoVPqEcRb7rHEy3G0L8", "ChIJBRk3gUlOqEcR9oEBV-dqK5M"]},
{placeIds: ["ChIJx8Iw5VFOqEcRXUgfWxkmAaA", "ChIJSdWeck5OqEcReSTr3YfoSuE"]},
{placeIds: ["ChIJ1WCXcFJOqEcRBR_ICa3TemU"]}]
This is the graphql mutation:
const SUBMIT_ITINERARY = gql`
mutation submitItinerary(
$dayPlans: [DayPlanInput]
){
submitItinerary(
dayPlans: $dayPlans
){
id
dayPlans{
placeIds
}
createdAt
}
}
`
This is the apollo react hook useMutation:
const [submitItinerary] = useMutation(SUBMIT_ITINERARY, {
update(result){
console.log(result.data);
},
onError(err){
console.log(err)
},
variables: {
dayPlans: itinerary
}
})
in my graphql playground I got this
So I thought in my frontend I would get something similar but instead I got this:
Why is that the case? I want to get a structure similar to what I got in graphql playground.
More information that might be helpful:
Here is my itinerary schema:
const itinerarySchema = new Schema({
city: String,
username: String,
createdAt: String,
dayPlans: [],
user: {
type: Schema.Types.ObjectId,
ref: 'users',
}
});
my type definitions:
type DayPlan {
placeIds: [String]!
}
type Itinerary {
id: ID!
dayPlans: [DayPlan]!
username: String!
createdAt: String!
}
input DayPlanInput {
placeIds: [String]
}
input RegisterInput {
username: String!
password: String!
confirmPassword: String!
email: String!
}
type Query {
getUsers: [User]
}
type Mutation {
register(registerInput: RegisterInput): User!
login(username: String!, password: String!): User!
submitItinerary(dayPlans: [DayPlanInput] ): Itinerary!
}
and my submitItinerary resolver:
Mutation: {
async submitItinerary(_, {dayPlans}, context) {
const user = checkAuth(context);
//console.log(user);
if (dayPlans.length === 0){
throw new Error('Itinerary should not be empty');
}
const newItinerary = new Itinerary({
dayPlans,
user: user.id,
username: user.username,
createdAt: new Date().toISOString()
})
const submitted = await newItinerary.save()
return submitted;
}
}
The first variable of the update option in useMutation is the Apollo Cache itself not the mutation result. The mutation result can be found in the second parameter. Here you can find the API docs.
This should log the correct data:
const [submitItinerary] = useMutation(SUBMIT_ITINERARY, {
update(cache, result){
console.log(result.data);
},
onError(err){
console.log(err)
},
variables: {
dayPlans: itinerary
}
})
Also another way to access the mutation result is using the second element from the hook return value:
const [submitItinerary, { data, loading, error }] = useMutation(SUBMIT_ITINERARY)

Adding field to Graphcool email-password template mutation

I used the email-password template to setup my graphcool server with user authentication. I want to add a property for “name” when signing up a new user. I added name to the User model and updated the signup.graphql and signup.ts code as shown below.
I get an error when running the createUser mutation which says “name is not defined”. I’m not sure what the problem is. Any help is greatly appreciated!
signup.graphql
extend type Mutation {
signupUser(email: String!, password: String!, name: String): SignupUserPayload
}
signup.ts
interface EventData {
email: string
password: string
name: string
}
const SALT_ROUNDS = 10
export default async (event: FunctionEvent<EventData>) => {
console.log(event)
try {
const graphcool = fromEvent(event)
const api = graphcool.api('simple/v1')
const { email, password } = event.data
if (!validator.isEmail(email)) {
return { error: 'Not a valid email' }
}
// check if user exists already
const userExists: boolean = await getUser(api, email)
.then(r => r.User !== null)
if (userExists) {
return { error: 'Email already in use' }
}
// create password hash
const salt = bcrypt.genSaltSync(SALT_ROUNDS)
const hash = await bcrypt.hash(password, salt)
// create new user
const userId = await createGraphcoolUser(api, email, hash, name)
// generate node token for new User node
const token = await graphcool.generateNodeToken(userId, 'User')
return { data: { id: userId, token } }
} catch (e) {
console.log(e)
return { error: 'An unexpected error occured during signup.' }
}
}
async function getUser(api: GraphQLClient, email: string): Promise<{ User }> {
const query = `
query getUser($email: String!) {
User(email: $email) {
id
}
}
`
const variables = {
email,
}
return api.request<{ User }>(query, variables)
}
async function createGraphcoolUser(api: GraphQLClient, email: string, password: string, name: string): Promise<string> {
const mutation = `
mutation createGraphcoolUser($email: String!, $password: String!, $name: String) {
createUser(
email: $email,
password: $password,
name: $name
) {
id
}
}
`
const variables = {
email,
password: password,
name: name
}
return api.request<{ createUser: User }>(mutation, variables)
.then(r => r.createUser.id)
Found the answer. I was missing name from this line here..
const { email, password } = event.data
Changing to this solved it
const { email, password, name } = event.data

Resources