I am working on a class project where I am creating a fullstack website using Apoll Server with express on the back end and React for the front end. I am trying to allow users to sign up and then from their dashboard page add a course. Right now the course just have courseTitle, description, and creator which is the user's id. I am able to add a course from the graphql playground but when I try it from the front end I get an error:
index.ts:58 Uncaught (in promise) Error: Response not successful: Received status code 400
I have gone over all the code and compared it to other code for signing up a user which works but I cannot get anotgher other than that error. I tried putting a console.log in resolver mutation but get nothing so somehow it isn't hitting the resolver at all.
Here is my addCourse resolver mutation:
export const CREATE_COURSE = gql`
mutation addCourse(
$courseTitle: String!
$description: String!
$creator: String!
) {
addCourse(
courseTitle: $courseTitle
description: $description
creator: $creator
) {
_id
courseTitle
description
creator {
_id
firstName
}
}
}
`;
Here is the resolver mutation:
addCourse: async (parent, args, context) => {
//if (context.user) {
const course = await Course.create(args);
return course;
//}
},
Here is the typeDef
const { gql } = require("apollo-server-express");
const typeDefs = gql`
type User {
_id: ID!
email: String
firstName: String
lastName: String
createdCourses: [Course]
enrolledCourseIds: [ID]
enrolledCourses: [Course]
}
type Course {
_id: ID
courseTitle: String!
description: String!
creator: User
}
type Mutation {
addCourse(
courseTitle: String!
description: String!
creator: String!
): Course
}
`;
// export the typeDef
module.exports = typeDefs;
This is the add course form:
import { Form, Field } from "react-final-form";
import { useMutation } from "#apollo/client";
import { useNavigate } from "react-router-dom";
import { CREATE_COURSE } from "../utils/mutations";
export const CreateCourse = () => {
const [addCourseMutation] = useMutation(CREATE_COURSE);
const navigate = useNavigate();
return (
<Form
onSubmit={async (values) => {
await addCourseMutation({
variables: {
...values,
},
onCompleted: (data) => {
console.log(data);
navigate("/dashboard");
},
});
}}
initialValues={{
courseTitle: "",
description: "",
}}
render={({ values, handleSubmit, form }) => {
return (
<div>
<br />
<br />
<h1>Course Title</h1>
<Field name="courseTitle" component="input" />
<h1>Description</h1>
<Field name="description" component="input" />
<button
disabled={
values?.password?.length === 0 || values?.email?.length === 0
}
onClick={async () => {
await handleSubmit();
form.reset();
}}
>
Submit
</button>
</div>
);
}}
/>
);
};
I made a bunch of changes trying to get the user Id for creator and took them out since I wasn't geting anything but that same error now matter what I did.
I realized that I wasn't sending the user _id from the form the form submission so I gut the user Id and set it in the initial state so it was passed to the resolver. Maybe not be the best way to do it, but I got it working.
import jwt from "jwt-decode";
import Auth from "../utils/auth";
import { Form, Field } from "react-final-form";
import { useMutation } from "#apollo/client";
import { useNavigate } from "react-router-dom";
import { CREATE_COURSE } from "../utils/mutations";
export const CreateCourse = () => {
const token = Auth.loggedIn() ? Auth.getToken() : null;
const user = jwt(token);
const [addCourseMutation] = useMutation(CREATE_COURSE);
const navigate = useNavigate();
return (
<Form
onSubmit={async (values) => {
await addCourseMutation({
variables: {
...values,
},
onCompleted: (data) => {
console.log(data);
navigate("/courses");
},
});
}}
initialValues={{
courseTitle: "",
description: "",
creator: user.data._id,
}}
render={({ values, handleSubmit, form }) => {
return (
<div>
<br />
<br />
<h1>Course Title</h1>
<Field name="courseTitle" component="input" />
<h1>Description</h1>
<Field name="description" component="input" />
<button
onClick={async () => {
await handleSubmit();
form.reset();
}}
>
Submit
</button>
</div>
);
}}
/>
);
};
Related
I am trying to figure out how to access the object.id on the update mutation I have made in my app.
I have an object called IssueGroup. I have made create and delete mutations and for the most part, they work okay. I'm getting stuck with the update mutation because I can't seem to get it to recognise the id of the object I'm trying to update.
I have a form with:
import * as React from "react"
import { gql } from "#apollo/client"
import type { IssueGroupInput } from "lib/graphql"
import { QueryMode, Role, SortOrder, useAllIssueGroupsQuery, useDeleteIssueGroupMutation, useUpdateIssueGroupMutation } from "lib/graphql"
import { AdminCreateIssueGroupForm } from "components/AdminCreateIssueGroupForm"
import { AdminUpdateIssueGroupForm } from "components/AdminUpdateIssueGroupForm"
import { Modal } from "components/Modal"
const __ = gql`
query AllIssueGroups {
allIssueGroups {
id
title
description
}
}
mutation deleteIssueGroup($id: String!) {
deleteIssueGroup(id: $id) {
id
title
description
issues
}
}
`
export default function IssueGroups() {
const [selectedIssueGroups, setSelectedIssueGroups] = React.useState<string[]>([])
const modalProps = useDisclosure()
const modalPropsUpdate = useDisclosure()
const [deleteIssueGroup] = useDeleteIssueGroupMutation()
const [updateIssueGroup] = useUpdateIssueGroupMutation()
const { data: issueGroup, refetch: refetchAllIssueGroups } = useAllIssueGroupsQuery()
const { data, loading, refetch } = useAllIssueGroupsQuery()
const allIssueGroups = data?.allIssueGroups
const onDeleteIssueGroup = (id: string) => {
return (
deleteIssueGroup({ variables: { id } }).then(() => refetch())
)
}
return (
<Box>
<Wrap mb={4} spacing={2}>
<Button
onClick={modalProps.onOpen}
}}
>
Create issue group
</Button>
</Wrap>
<Modal {...modalProps} title="Create Issue Group">
<AdminCreateIssueGroupForm onClose={modalProps.onClose} />
</Modal>
<IconButton
onClick={modalPropsUpdate.onOpen}
/>
<Modal {...modalPropsUpdate} title="Update Issue Group">
<AdminUpdateIssueGroupForm onClose=
{modalPropsUpdate.onClose} />
</Modal>
<IconButton
onClick={() => onDeleteIssueGroup(issueGroup.id)}
/>
))}
)
}
Then, I have a modal that opens when the update button is clicked that has:
import * as React from "react"
import { gql } from "#apollo/client"
import { useRouter } from "next/router"
import { useAllIssueGroupsQuery, useUpdateIssueGroupMutation, IssueGroupInput } from "lib/graphql"
import { useForm } from "lib/hooks/useForm"
import Yup from "lib/yup"
const _ = gql`
query AllIssueGroups {
allIssueGroups {
id
title
description
issues
}
}
mutation updateIssueGroup($id: String!, $data: IssueGroupInput!) {
updateIssueGroup(id: $id, data: $data) {
id
title
description
issues
}
}
`
interface Props {
onClose: () => void
}
const IssueGroupSchema = Yup.object().shape({
title: Yup.string().required(),
description: Yup.string().required(),
})
export function AdminUpdateIssueGroupForm(props: Props) {
const router = useRouter()
const [updateIssueGroup] = useUpdateIssueGroupMutation()
const { data: issueGroups, refetch: refetchAllIssueGroups } = useAllIssueGroupsQuery()
const form = useForm({ schema: IssueGroupSchema })
const handleSubmit = async(data:IssueGroupInput) => {
console.log("made it to the handle submit before success handler")
return await form.handler(() => updateIssueGroup({
variables: {
id: issueGroup.id,
// I get an error that says Cannot find name 'issueGroup'. Did you mean 'issueGroups'.
// I know that's because I'm using the AllIssueGroups query to find many,
// but I don't know how to write the query to find the specific one I
// want to edit when I press the edit button in the form above
data: { ...data }
} }), {
onSuccess: (res, toast) => {
console.log("made it to the form handler success")
toast({ description: "Issue group updated" })
form.reset()
props.onClose()
},
})
}
return (
<Form {...form} onSubmit={handleSubmit}>
<Stack>
<Input name="title" label="Title" />
{/* <Input name="description" label="Description" /> */}
{/* <Text mb='8px' fontWeight="medium" fontSize="sm" > Description</Text> */}
<Textarea name="description" label="Describe" rows={4}/>
<Button onClick={props.onClose}>Cancel</Button>
type="submit"
>
Create
</Form>
)
}
My IssueGroup resolver has:
#Mutation(() => IssueGroup)
async updateIssueGroup(
#Arg("id") id: string,
#Arg("data") data: IssueGroupInput
) {
return await this.issueGroupService.updateIssueGroup(id, data)
}
#Query(() => IssueGroup)
async issueGroup(#Arg("id") id: string) {
return await this.issueGroupService.getIssueGroup(id)
}
The IssueGroup service has:
async updateIssueGroup(id: string, data: IssueGroupInput) {
const issueGroup = await prisma.issueGroup.findUnique({ where: { id } })
if (!issueGroup) {
throw new Error("Issue not found")
}
return await prisma.issueGroup.update({ where: { id }, data })
}
async getIssueGroup(id: string) {
return await prisma.issueGroup.findUnique({
where: {
id,
},
})
}
How can I tell the modal form for update what the specific issueGroup.id related to the button clicked to open the modal is?
The prisma schema has:
model IssueGroup {
id String #id #default(dbgenerated("gen_random_uuid()")) #db.Uuid
title String
description String
issues Issue[]
createdAt DateTime #default(now()) #db.Timestamptz(6)
updatedAt DateTime #default(now()) #updatedAt #db.Timestamptz(6)
}
model Issue {
id String #id #default(dbgenerated("gen_random_uuid()")) #db.Uuid
title String
description String
issueGroup IssueGroup? #relation(fields: [issueGroupId], references: [id], onDelete: SetNull, onUpdate: Cascade)
issueGroupId String? #db.Uuid
subscribers UserIssue[]
createdAt DateTime #default(now()) #db.Timestamptz(6)
updatedAt DateTime #default(now()) #updatedAt #db.Timestamptz(6)
}
NEXT ATTEMPT
I have made a further attempt, which tries to give the modal the issueGroup.id with the onClick handler, but I still can't get the form to recognise the id, and this version of this attempt generates an error message I don't know how to break down. It says:
Type '{ onClose: () => void; issueGroup: Omit<IssueGroup, "createdAt"
| "updatedAt" | "issues"> | null; }' is not assignable to type
'IntrinsicAttributes & Props'. Property 'issueGroup' does not exist
on type 'IntrinsicAttributes & Props'.
This time:
I tried to set state:
import { QueryMode, Role, SortOrder, useAllIssueGroupsQuery, useDeleteIssueGroupMutation, useUpdateIssueGroupMutation, IssueGroup as IssueGroupGQLType } from "lib/graphql"
const [selectedIssueGroup, setSelectedIssueGroup] = React.useState<Omit<
IssueGroupGQLType,
"createdAt" | "updatedAt" | "issues"
> | null>(null)
the update button has:
<IconButton
aria-label='Update Issue Group'
// onClick={modalPropsUpdate.onOpen}
onClick={() => {
setSelectedIssueGroup(issueGroup)
modalPropsUpdate.onOpen()
<Modal {...modalPropsUpdate } title="Update Issue Group">
<AdminUpdateIssueGroupForm onClose={modalPropsUpdate.onClose} issueGroup ={selectedIssueGroup} />
</Modal>
Then, in the form, I tried to read issueGroup.id:
const _ = gql`
query AllIssueGroups {
allIssueGroups {
id
title
description
issues
}
}
mutation updateIssueGroup($id: String!, $data: IssueGroupInput!) {
updateIssueGroup(id: $id, data: $data) {
id
title
description
issues
}
}
`
interface Props {
onClose: () => void
issueGroup: selectedIssueGroup
}
const IssueGroupSchema = Yup.object().shape({
title: Yup.string().required(),
description: Yup.string().required(),
})
export function AdminUpdateIssueGroupForm(props: Props) {
const router = useRouter()
const [updateIssueGroup] = useUpdateIssueGroupMutation()
// const { data: issueGroups, refetch: refetchAllIssueGroups } = useAllIssueGroupsQuery()
const form = useForm({ schema: IssueGroupSchema })
const handleSubmit = async( data:IssueGroupInput) => {
// await form.triggerValidation()
console.log("made it to the handle submit before success handler")
The variables below don't know what issueGroup is
return await form.handler(() => updateIssueGroup({ variables: { id: issueGroup.id, data: { ...data } } }), {
onSuccess: (res, toast) => {
console.log("made it to the form handler success")
form.reset()
props.onClose()
},
})
}
return (
<Form {...form} onSubmit={handleSubmit}>
<Stack>
<Input name="title" label="Title" />
<Textarea name="description" label="Describe" rows={4}/>
<FormError />
<ButtonGroup>
<Button onClick={props.onClose}>Cancel</Button>
<Button
type="submit"
isLoading={form.formState.isSubmitting}
isDisabled={form.formState.isSubmitting}
>
Create
</Button>
</ButtonGroup>
</Stack>
</Form>
)
}
With this attempt, my form still doesnt know what selectedIssueGroup or IssueGroup mean.
I have seen this page of the react docs, and I think the bit I am missing is that I haven't wrapped my form in the equivalent of isActive. The problem is, I don't know where to put selectedIssueGroup. My definition of that state is in a different file to the file the form is is saved in.
In an attempt to apply the logic in this documentation, I tried changing the modal so that is wrapped inside the state, as follows:
{selectedIssueGroup &&
<Modal {...modalPropsUpdate } title="Update Issue Group">
<AdminUpdateIssueGroupForm onClose={modalPropsUpdate.onClose} issueGroup ={selectedIssueGroup} />
</Modal>
}
I don't get any new errors, but it also doesn't work. My form still doesn't know what the issueGroup is.
I can see that the update button knows what the issueGroup is and I can see that the Modal knows it too. I cant figure out how to give that value to the update form.
I have seen this tutorial, which shows how to use the create form to make an edit. The screen shot it uses displays the created information populated in the edit form, but it does not show how it found that data to use in that form. I can't find an example of how to do that.
NEXT ATTEMPT 21 DEC
In this most recent attempt, I have tried to add the issueGroup reference as a property on the Modal. Even if this worked, I think it would not be helpful for a reason explained below. However, i don't understand why it doesn't work.
<Modal {...modalPropsUpdate } issueGroup={selectedIssueGroup} title="Update Issue Group" >
When I try this, I get an error (VS code underlines the issueGroup in the above line). The error message says:
Type '{ children: (void | Element)[]; issueGroup: Omit<IssueGroup, "createdAt" | "updatedAt" | "issues"> | null; title: string; isOpen:
boolean; onOpen: () => void; ... 4 more ...; getDisclosureProps:
(props?: any) => any; }' is not assignable to type
'IntrinsicAttributes & Props & { children?: ReactNode; }'.
Property 'issueGroup' does not exist on type 'IntrinsicAttributes & Props & { children?: ReactNode; }'.
I don't know what this means. The console logs inside the update button, and above and below the modal all know what issueGroup is.
The reason I think it is not required is that I can log the value of the issue Group above the modal and above the component inside the modal that loads the form. Both logs show the value of the issueGroup that I want to use in the form.
I'm trying to do that as follows:
const CheckIfModalKnowsIssueGroupId = props => {
console.log("checking if modal knows the issue group", props.toLog);
return (
<div>
{props.children}
</div>
);
};
ALTERNATE for attempt to log inside the form component
<AdminUpdateIssueGroupForm
onClose={modalPropsUpdate.onClose}
issueGroup ={selectedIssueGroup}
>
<CheckIfModalKnowsIssueGroupId toLog={selectedIssueGroup} />
</AdminUpdateIssueGroupForm>
NEXT ATTEMPT - READ MODAL VALUES
In a further attempt to try and make the modal carry the value of IssueGroup, I am trying to do the following:
{selectedIssueGroup && modalPropsUpdate(isOpen:true) <Modal {...modalPropsUpdate } issueGroup={selectedIssueGroup} title="Update Issue Group" >
This attempt is wrong because I don't know how to engage with modalPropsUpdate. The above formulation generates an error in the isOpen test as follows:
const modalPropsUpdate: {
isOpen: boolean;
onOpen: () => void;
onClose: () => void;
onToggle: () => void;
isControlled: boolean;
getButtonProps: (props?: any) => any;
getDisclosureProps: (props?: any) => any;
}
This expression is not callable. Type '{ isOpen: boolean; onOpen: ()
=> void; onClose: () => void; onToggle: () => void; isControlled: boolean; getButtonProps: (props?: any) => any; getDisclosureProps:
(props?: any) => any; }' has no call signatures.
I can't find syntax that does not produce an error.
The objective of this attempt is to see if the modal is open, and still knows what the selectedIssueGroup is.
NEW OBSERVATION
I have seen this repo. I cannot find an example using the structure I have adopted to allow the id to be communicated to the form.
NEXT ATTEMPT 23 DEC
I tried to rule out the source of the problem as the selectedIssueGroup state handler being set in a different component to the form I was trying to use to update the object. So, I moved the form into the same file. It did not work. The udpate form handler still does not know what issueGroup.id or selectedIssueGroup mean.
import * as React from "react"
import { gql } from "#apollo/client"
import { useRouter } from "next/router"
import type { IssueGroupInput } from "lib/graphql"
import { QueryMode, Role, SortOrder, useAllIssueGroupsQuery, useDeleteIssueGroupMutation, useUpdateIssueGroupMutation, IssueGroup as IssueGroupGQLType } from "lib/graphql"
// ,
import { AdminCreateIssueGroupForm } from "components/AdminCreateIssueGroupForm"
import Yup from "lib/yup"
const __ = gql`
query AllIssueGroups {
allIssueGroups {
id
title
description
}
}
mutation updateIssueGroup($id: String!, $data: IssueGroupInput!) {
updateIssueGroup(id: $id, data: $data) {
id
title
description
issues
}
}
`
const CheckIfModalKnowsIssueGroupId = props => {
console.log("checking if modal knows the issue group", props.toLog);
return (
<div>
{props.children}
</div>
);
};
const IssueGroupSchema = Yup.object().shape({
title: Yup.string().required(),
description: Yup.string().required(),
})
const form = useForm({ schema: IssueGroupSchema })
export default function IssueGroups() {
const [selectedIssueGroups, setSelectedIssueGroups] = React.useState<string[]>([])
const modalProps = useDisclosure()
const modalPropsUpdate = useDisclosure()
const [deleteIssueGroup] = useDeleteIssueGroupMutation()
const { data: issueGroup, refetch: refetchAllIssueGroups } = useAllIssueGroupsQuery()
const [selectedIssueGroup, setSelectedIssueGroup] = React.useState<Omit<
IssueGroupGQLType,
"createdAt" | "updatedAt" | "issues"
> | null>(null)
interface Props {
onClose: () => void
issueGroup: typeof selectedIssueGroup
}
function AdminUpdateIssueGroupForm(props: Props) {
const router = useRouter()
const [updateIssueGroup] = useUpdateIssueGroupMutation()
const handleSubmitUpdate = async( data:IssueGroupInput) => {
// await form.triggerValidation()
console.log("made it to the handle submit before success handler")
return await form.handler(() => updateIssueGroup({ variables: { id: issueGroup.id, data: { ...data } } }), {
onSuccess: (res, toast) => {
console.log("made it to the form handler success")
toast({ description: "Issue group updated" })
form.reset()
props.onClose()
},
})
}
const { data, loading, refetch } = useAllIssueGroupsQuery(
)
const allIssueGroups = data?.allIssueGroups
const onDeleteIssueGroup = (id: string) => {
return (
deleteIssueGroup({ variables: { id } }).then(() => refetch())
)
}
return (
<Wrap mb={4} spacing={2}>
<Button
onClick={modalProps.onOpen}
>
Create issue group
</Button>
</Wrap>
<Modal {...modalProps} title="Create Issue Group">
<AdminCreateIssueGroupForm onClose={modalProps.onClose} />
</Modal>
{data?.allIssueGroups.map((issueGroup) => (
<Tr key={issueGroup.id}>
<Text textStyle="h6">{issueGroup.title}</Text>
<IconButton
onClick={() => {
setSelectedIssueGroup(issueGroup)
modalPropsUpdate.onOpen()
}}
/>
{ console.log("update knows what the issuegroup is", selectedIssueGroup)}
<Modal {...modalPropsUpdate } title="Update Issue Group" >
<Form {...form} onSubmit={handleSubmitUpdate} >
<Stack>
<Input name="title" label="Title" />
<ButtonGroup>
<Button onClick={props.onClose}>Cancel</Button>
<Button
type="submit"
>
Save changes
</Button>
</ButtonGroup>
</Stack>
</Form>
</Modal>
<IconButton
onClick={() => onDeleteIssueGroup(issueGroup.id)}
/>
</ButtonGroup>
From the code you posted for AdminUpdateIssueGroupForm, there isn't a variable defined as issueGroup here:
export function AdminUpdateIssueGroupForm(props: Props) {
const router = useRouter()
const [updateIssueGroup] = useUpdateIssueGroupMutation()
const { data: issueGroups, refetch: refetchAllIssueGroups } = useAllIssueGroupsQuery()
const form = useForm({ schema: IssueGroupSchema })
const handleSubmit = async(data:IssueGroupInput) => {
// await form.triggerValidation()
console.log("made it to the handle submit before success handler")
return await form.handler(() => updateIssueGroup({
variables: {
// ========== issueGroup has not yet been defined ===========
// ========== Do you need to pull anything out of the 'data' variable to get the right issueGroup variable? ===========
id: issueGroup.id,
If you are looking to update the data from a single issueGroup instance that derives from the issueGroups variable, it looks like it would derive from the data variable you defined at the handleSubmit function.
A typical pattern for a list in react with an onClick action on an item where you want to use the id is:
list.map(el => (
<div key={el.id} onclick={() => clickHandler(el.id)>
... the rest of your layout
</div>
)
where the clickHandler function is whatever function you want to run on click. That function should accept the object's id as a parameter. That function can in turn call the form's submit handler should you be using forms.
I think your attempt 2 will work fine if you do this
You are setting a state and passing it to component like
<AdminUpdateIssueGroupForm onClose={modalPropsUpdate.onClose} issueGroup={selectedIssueGroup} />
and getting the props in AdminUpdateIssueGroupForm like ,
function AdminUpdateIssueGroupForm(props: Props) {
so you should access issueGroup.id like this in form handler,
return await form.handler(() => updateIssueGroup({ variables: { id: props.issueGroup.id, data: { ...data } } }), {
Note: In your last attempt also, you can access props.issueGroup.id
UPDATE:
for the selectedIssueGroup issue, i believe you are talking about the interface Props here.
In that interface you should provide a type to issueGroup not a state variable.So try this,
declare a type before usestate as
type IssueGroupType = Omit<
IssueGroupGQLType,
"createdAt" | "updatedAt" | "issues"
> | null
and use it in both state and your interface
State:
const [selectedIssueGroup, setSelectedIssueGroup] = React.useState<IssueGroupType>(null)
interface:
interface Props {
onClose: () => void
issueGroup: IssueGroupType
}
You need to create a separate handler for the Edit button and set some state, then just pass that state in editFormModal component like the code is given below. To test the code, you can click Here
export default function IssueGroups() {
const toast = useToast()
const [isOpen, setIsOpen] = useState(false);
const [loadGreeting, { error, called, loading, data }] = useLazyQuery(
GET_ALL_ISSUE_GROUPS,
{
fetchPolicy: 'no-cache',
}
);
const [modalValue, setModalValue] = useState({});
function handleEditClick(data) {
setModalValue(data);
setIsOpen(true);
}
React.useEffect(() => {
loadGreeting();
}, []);
const setUpdatedData = (obj) => {
toast({
title: 'Update Success.',
description: "Issue group is updated successfuly",
status: 'success',
duration: 5000,
isClosable: true,
})
loadGreeting()
setIsOpen(false);
};
if (error) return <p>Error : {error.message}</p>;
return (
<>
<Modal
onClose={() => {
setIsOpen(false);
}}
isOpen={isOpen}
size="full"
>
<AdminUpdateIssueGroupForm
IssueGroupData={modalValue}
setUpdatedData={setUpdatedData}
/>
</Modal>
{!loading && <TableContainer>
<Table variant="simple">
{/* <TableCaption>Imperial to metric conversion factors</TableCaption> */}
<Thead>
<Tr>
<Th>Title</Th>
<Th>Description</Th>
<Th>Date</Th>
<Th>Action</Th>
</Tr>
</Thead>
<Tbody>
{data &&
data.IssueGroup.map((group) => (
<Tr key={group.id}>
<Td>{group.title}</Td>
<Td>{group.description}</Td>
<Td>{group.createdAt}</Td>
<Td>
<Button
onClick={() => {
handleEditClick(group);
}}
>
{' '}
Edit{' '}
</Button>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>}
</>
);
}
I'm building a program for people to organise potlucks using the MERN stack.
I have a form for people to fill out, and if I console log what's coming through from that in the action, it is taking all of the fields, but after I do this in the action:
export const createPotluck = (potluck) => async (dispatch) => {
try {
const { data } = await api.createPotluck(potluck);
console.log("data", data)
console.log("potluck", potluck)
dispatch({ type: "CREATE", payload: data});
} catch (error) {
console.log(error);
}
};
It is only passing through one field. When I console log "potluck", I get everything:
{potluckHost: "host", potluckTitle: "title", potluckTheme: "theme", essentials: Array(3)}essentials: (3) ["1", "2", "3"]potluckHost: "host"potluckTheme: "theme"potluckTitle: "title"[[Prototype]]: Object
But when I console log "data", I only get the "essentials" array:
{essentials: Array(3), _id: "61320fec40906afff8aed63c", __v: 0}
I have spent ages working on this and I just cannot understand why it's happening like this. I'm basing the structure of it on a tutorial I followed which works absolutely no problem, so I'm really now at my whits end.
Here's (what I think are...) the relevant bits of code - but could it be that I'm doing something wrong in the controller or something? Just in case, the whole thing is on github here: https://github.com/gordonmaloney/whatLuck-mern
Here's the CreatePotluck form:
import React, { useState, useEffect } from "react";
import { TextField, Button, Typography, Paper } from "#material-ui/core";
import { useDispatch } from 'react-redux';
import { createPotluck } from '../actions/potlucks'
const CreatePotluck = ( ) => {
const [potluckData, setPotluckData] = useState({ potluckHost: "", potluckTitle: "", potluckTheme: "", essentials: "" });
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault()
dispatch(createPotluck(potluckData));
}
return (
<Paper>
<form autoComplete="off" noValidate onSubmit={handleSubmit} >
<Typography variant="h6">Create a Potluck</Typography>
<TextField name="host" variant="outlined" label="Potluck Host" fullWidth value={potluckData.potluckHost} onChange={(e) => setPotluckData({ ...potluckData, potluckHost: e.target.value }) } />
<TextField name="title" variant="outlined" label="Potluck Title" fullWidth value={potluckData.potluckTitle} onChange={(e) => setPotluckData({ ...potluckData, potluckTitle: e.target.value })} />
<TextField name="theme" variant="outlined" label="Potluck Theme" fullWidth value={potluckData.potluckTheme} onChange={(e) => setPotluckData({ ...potluckData, potluckTheme: e.target.value }) } />
<TextField name="essentials" variant="outlined" label="Essentials (coma separated)" fullWidth value={potluckData.essentials} onChange={(e) => setPotluckData({ ...potluckData, essentials: e.target.value.split(',') })} />
<Button variant="contained" color="primary" size="large" type="submit" fullWidth>Submit</Button>
</form>
</Paper>
);
}
export default CreatePotluck
The action is as above, and the API call is here:
import axios from 'axios';
const url = 'http://localhost:5001/potlucks';
export const fetchPotlucks = () => axios.get(url);
export const createPotluck = (newPotluck) => axios.post(url, newPotluck)
And the controller:
export const createPotluck = async (req, res) => {
const potluck = req.body;
const newPotluck = new PotluckBody(potluck);
try {
await newPotluck.save();
console.log("controler", potluck)
res.status(201).json(newPotluck)
} catch (error) {
res.status(409).json({message: error})
}
}
Thanks so much in advance folks, and sorry if this is a daft question - I'm v new to dabbling in backend 🙌
I solved the issue. I had a mismatch in how I'd call things from the form and the MongoDB schema model. I updated that and now it works fine.
In my Model, I had originally had this:
import mongoose from 'mongoose';
const postSchema = mongoose.Schema({
title: String,
theme: String,
host: String,
essentials: [String]
});
const PotluckBody = mongoose.model('PotluckBody', postSchema)
export default PotluckBody;
But I later updated the names of elements elsewhere to potluckTitle, potluckTheme, potluckHost, and essentials. That meant that when the data was being sent to the model, the ones that had had their name changed were vanishing, but essentials was passing fine because it hadn't changed. I updated the file to this:
import mongoose from 'mongoose';
const postSchema = mongoose.Schema({
potluckTitle: String,
potluckTheme: String,
potluckHost: String,
essentials: [String],
});
const PotluckBody = mongoose.model('PotluckBody', postSchema)
export default PotluckBody;
And now it works great 😁
The resolver works fine when i use it from the Playground. But when i send values from the client to the resolver, values somehow show up "undefined" in the resolver.
Client:
import React, { useState } from 'react';
import { useQuery, useMutation } from '#apollo/react-hooks';
import { Form, Grid } from 'semantic-ui-react';
import gql from 'graphql-tag';
function TestPage() {
const userId = "5fa2c177382ce83660b3911b";
const { loading, data: { getUser } = {} } = useQuery( FETCH_USER_QUERY, {
variables: {
userId
}
});
const [values, setValues] = useState({
countryInput: '',
descriptionInput: ''
})
const onChange = (event) => {
setValues({ ...values, [event.target.name]: event.target.value});
}
const [updateCountry] = useMutation(UPDATE_COUNTRY_MUTATION, {
variables: {
userId,
values
}
})
const submit = (event) => {
event.preventDefault();
updateCountry()
}
console.log(values)
let TestPage;
if(loading){
TestPage = (<p>Page Loading...</p>)
} else {
const {
username,
country,
description
} = getUser;
TestPage = (
<Grid columns={1}>
<Grid.Row className="profile-name">
<h1>Test Page</h1>
<h4>This page is for testing code</h4>
<h4>User: {username}</h4>
</Grid.Row>
<Grid.Row className="profile-name">
<h1>Country: {country}</h1>
<h1>Desc: {description}</h1>
</Grid.Row>
<Grid.Row>
<Form>
<input
style={{ marginBottom: 10 }}
placeholder="Country"
type="text"
name="countryInput"
value={values.countryInput}
onChange={onChange}
/>
<input
style={{ marginBottom: 10 }}
placeholder="Description"
type="text"
name="descriptionInput"
value={values.descriptionInput}
onChange={onChange}
/>
<button
onClick={submit}
className="ui button red"
color="red"
>
Save
</button>
</Form>
</Grid.Row>
</Grid>
)
}
return TestPage;
}
Mutation (in the same file as client):
const UPDATE_COUNTRY_MUTATION = gql `
mutation UpdateCountry(
$userId: ID!,
$countryInput: String,
$descriptionInput: String ){
updateCountry(
userId: $userId,
countryInput: $countryInput
descriptionInput: $descriptionInput
){
username
country
description
}
}
`;
I got "Unhandled Rejection (Error): Network error: Response not successful: Received status code 400" before, but now it just returns nulls in the db.
I assume the issue was the mutation, i changed it from using "input" to just typing out the variables, but doesent work.
I googled around and tried different kind of doing callbacks and hooks, but i get the unhandled rejection error and when i get it working the values show up undefined.
How can the value be defined when sent in the client but show up undefined in the resolver? What am i doing wrong?
"...values" instead of "values" in the useMutation:
const [updateCountry] = useMutation(UPDATE_COUNTRY_MUTATION, {
variables: {
userId,
...values
}
})
I'm trying tok create a simple demo CRUD app using React and Graphql
I have the backend all set and working.
On the front end I'm using React with useQuery and useMutation.
I have the create working with useMutation to create the data and the read with useQuery to show the data.
I now stuck with the delete. I have a button the post and I can get the id of the post to pass to the useMutation function to delete the post.
I'm just stuck getting it to work if anyon can help.
App.tsx
import React, { useState } from 'react';
import './App.css';
import { RecipeData } from '../generated/RecipeData';
import { GET_ALL_RECIPES, ADD_RECIPE, DELETE_RECIPE } from '../queries';
import { useQuery, useMutation } from 'react-apollo-hooks';
const App: React.FC = () => {
const [name, setName] = useState<string>('')
const [description, setDes] = useState<string>('')
const [id, setId] = useState<string>('')
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value)
}
const handleDesChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setDes(e.target.value)
}
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault()
createRecipe()
};
const handelDelete = (e: React.MouseEvent<HTMLButtonElement>) => {
setId(e.target.parentElement.getAttribute("data-id"))
deleteRecipe()
}
const [deleteRecipe] = useMutation(DELETE_RECIPE, {
variables: { id }, refetchQueries: ['RecipeData']
})
const [createRecipe, { error }] = useMutation(ADD_RECIPE, {
variables: { name, description }, refetchQueries: ['RecipeData']
})
if (error) {
console.error('erroring : ', error)
}
const { data, loading } = useQuery<RecipeData | null>(GET_ALL_RECIPES, {
suspend: false
})
if (loading || !data) return <div>Loading</div>
return (
<div className="App">
<h1>Graphql</h1>
<ul>
{
data.recipe !== null && data.recipe.map((recipe, i) => (
<li key={recipe._id} data-id={recipe._id}>
{recipe.name}
<button onClick={handelDelete}>X</button>
</li>
))
}
</ul>
<form>
<div>
<label>Name</label>
<input
type="text"
value={name}
onChange={handleNameChange}
/>
</div>
<div>
<label>Description</label>
<input
type="text"
value={description}
onChange={handleDesChange}
/>
</div>
<button onClick={handleClick}>Add Recipe</button>
</form>
</div>
);
}
export default App;
queries/index.tsx
import { gql } from 'apollo-boost';
export const GET_ALL_RECIPES = gql`
query RecipeData{
recipe{
_id
name
description
}
}
`
export const ADD_RECIPE = gql`
mutation addRecipe($name: String!, $description:String){
addRecipe(name: $name, description: $description){
_id
name
description
}
}
`
export const DELETE_RECIPE = gql`
mutation deleteRecipe($id: Int!){
deleteRecipe(_id: $id){
_id
name
description
}
}
`
Server side
schema.js
exports.typeDefs = `
type Recipe{
_id: ID
name: String
description: String
}
type Query{
recipe:[Recipe]
}
type Mutation{
addRecipe(name: String!, description: String):Recipe
deleteRecipe(_id: Int):Recipe
}
`
resolvers.js
exports.resolvers = {
Query:{
recipe: async(obj, args, {Recipe}, info) => {
const allRecipes = await Recipe.find()
return allRecipes
}
},
Mutation:{
addRecipe: async(obj, {name, description}, {Recipe}, info) => {
const newRecipe = await new Recipe({
name,
description
}).save()
return newRecipe
},
deleteRecipe: async(obj, args, {Recipe}, info, {_id}) => {
delete Recipe[_id];
}
}
}
I don't have a live demo sorry but this outputs a list of recipe name with an input field to add new recipes, this part works.
I also have a delete button after each recipe displayed. I wanted to click this button and have that recipe be removed from the list.
At the moment when I click this delete button I get the following error message in the network tab of the dev tools
message: "Variable "$id" got invalid value ""; Expected type Int. Int cannot represent non-integer value: """
const handelDelete = (e: React.MouseEvent<HTMLButtonElement>) => {
setId(e.target.parentElement.getAttribute("data-id"))
deleteRecipe()
}
setID() is needed for something (other than passing parameter) ?
id is not updated immediately by setID() (async updates like setState - see docs) - value can't be used in following deleteRecipe()
IMHO there should be:
const handelDelete = (e: React.MouseEvent<HTMLButtonElement>) => {
const delID = e.target.parentElement.getAttribute("data-id")
deleteRecipe( delID )
}
and deleting with direct parameter passing:
const deleteRecipe = (id) => useMutation(DELETE_RECIPE, {
variables: { id }, refetchQueries: ['RecipeData']
})
I have used same form to create and update account(module). Create is working fine but in update mode I am not able to set form field value using Set State methods. I have used query component on render methods and setstate not working on rendor method.
import { Mutation } from "react-apollo";
import { Query } from "react-apollo";
import gql from "graphql-tag";
import React, { Component } from "react";
import Router from "next/router";
import Joi from "joi-browser";
const CREATE_ACCOUNT = gql`
mutation CreateAccount(
$name: String
$phone_office: String
$website: String
) {
createAccount(name: $name, phone_office: $phone_office, website:
$website) {
name
phone_office
website
}
}
`;
export const allAccountbyidQuery = gql`
query account($id: String) {
account(id: $id) {
id
name
phone_office
website
}
};
const schema = {
name: Joi.string()
.required()
.error(errors => {
return {
message: "Name is required!"
};
}),
phone_office: Joi.string()
.required()
.error(errors => {
return {
message: "Phone Number is required!"
};
}),
website: Joi.string()
.required()
.error(errors => {
return {
message: "Website is required!"
};
})
};
Main class component
class CreateAccountModule extends React.Component {
static async getInitialProps({ query }) {
const { id } = query;
return { id };
}
constructor(props) {
super();
this.state = {
isFirstRender: true,
name: "",
phone_office: "",
website: ""
};
}
handleChange = event => {
console.log("hello");
const { name, value } = event.target;
this.setState({ [name]: value });
};
validate(name, phone_office, website) {
let errors = "";
const result = Joi.validate(
{
name: name,
phone_office: phone_office,
website: website
},
schema
);
if (result.error) {
errors = result.error.details[0].message;
}
return errors;
}
setName = name => {
if (this.state.isFirstRender) {
this.setState({ name, isFirstRender: false });
}
};
render() {
let input;
const { errors } = this.state;
console.log(this.props);
const allAccountbyidQueryVars = {
id: this.props.id
};
//console.log(allAccountbyidQueryVars);
return (
<Query
query={allAccountbyidQuery}
variables={allAccountbyidQueryVars}
onCompleted={data => this.setName(data.account.name)}
>
{({ data, loading, error }) => {
<CreateAccountModule account={data.account} />;
return (
<Mutation mutation={CREATE_ACCOUNT}>
{(createAccount, { loading, error }) => (
<div>
<form
onSubmit={e => {
e.preventDefault();
const errors = this.validate(
e.target.name.value,
e.target.phone_office.value,
e.target.website.value
);
if (errors) {
this.setState({ errors });
return;
}
if (!errors) {
let accountres = createAccount({
variables: {
name: e.target.name.value,
phone_office: e.target.phone_office.value,
website: e.target.website.value
}
}).then(() => Router.push("/"));
input.value = "";
}
}}
>
{errors && <p>{errors}</p>}
<input
type="text"
name="name"
id="name"
placeholder="Name"
value={this.state.name}
onChange={this.handleChange}
/>
<input
name="phone_office"
id="phone_office"
placeholder="Phone Number"
//value={data.account.phone_office}
//value="123456"
onChange={this.handleChange}
/>
<input
name="website"
id="website"
placeholder="Website"
//value={data.account.website}
onChange={this.handleChange}
/>
<button type="submit">Add Account</button>
<button type="button">Cancel</button>
</form>
{loading && <p>Loading...</p>}
{error && <p>Error :( Please try again</p>}
</div>
)}
</Mutation>
);
}}
</Query>
);
}
}
export default CreateAccountModule;
`
I have tried with props but get props data in apollo state. anyone please suggest possible solution to fix this issue.