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 😁
Related
I want to adjust a demo provided by some tutorial about React Design Patterns, subject: Higher Order Component, and want to use an external data source from the url:
https://jsonplaceholder.typicode.com/users/1
to display the data within my form.
I guess since it's an async call, my Form always displays the "Loading part". What's the best way to solve this issue to ultimately receive the data? I can clearly see response.data not being empty when I log it, but the State variables are when I log them inside of the useEffect Hook
This is what I got so far.
Any help, tips, additional sources to learn this would be highly appreciated.
This is my HOC which I just copied:
import React, { useState, useEffect } from "react";
import axios from "axios";
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
export const withEditableResource = (Component, resourcePath, resourceName) => {
return (props) => {
const [originalData, setOriginalData] = useState(null);
const [editedData, setEditedData] = useState(null);
useEffect(() => {
(async () => {
const response = await axios.get(resourcePath);
setOriginalData(response.data);
setEditedData(response.data);
})();
}, []);
const onChange = (changes) => {
setEditedData({ ...editedData, ...changes });
};
const onSave = async () => {
const response = await axios.post(resourcePath, {
[resourceName]: editedData,
});
setOriginalData(response.data);
setEditedData(response.data);
};
const onReset = () => {
setEditedData(originalData);
};
const resourceProps = {
[resourceName]: editedData,
[`onChange${capitalize(resourceName)}`]: onChange,
[`onSave${capitalize(resourceName)}`]: onSave,
[`onReset${capitalize(resourceName)}`]: onReset,
};
return <Component {...props} {...resourceProps} />;
};
};
That's my form, I want to use - in the last lines you can find the hard-coded URL path, I want to swap for a parameter once this problem is done:
import { withEditableResource } from "./withEditableResource";
export const UserInfoFormImproved = withEditableResource(
({ user, onChangeUser, onSaveUser, onResetUser }) => {
const { name, email, username } = user || {};
return user ? (
<>
<label>
Name:
<input
value={name}
onChange={(e) => onChangeUser({ name: e.target.value })}
/>
</label>
<label>
Email:
<input
value={email}
onChange={(e) => onChangeUser({ email: e.target.value })}
/>
</label>
<label>
Username:
<input
value={username}
onChange={(e) => onChangeUser({ username: e.target.value })}
/>
</label>
<button onClick={onResetUser}>Reset</button>
<button onClick={onSaveUser}>Save Changes</button>
</>
) : (
<p>Loading...</p>
);
},
`https://jsonplaceholder.typicode.com/users/3`,
"User"
);
And that's the actual use of this two components within my App - I've added my idea on how to solve the parameter argument here:
import { UserInfoFormImproved } from "./HigherOrderComponents/UserInfoFormImproved";
function App() {
return (
<UserInfoFormImproved userId={1} />
);
}
export default App;
I use the react-hook-form library to validate my forms, but I want my page not to reload after submitting the form so that I can redirect the user to the desired page on my own. For example, using the navigate hook from the react-router-dom library. How to stop page reloading?
My code:
import React from 'react';
import {signInWithEmailAndPassword, updateCurrentUser} from "firebase/auth";
import {auth} from "../firebase";
import {Link, useLocation, useNavigate} from "react-router-dom";
import styles from "./elements.module.css";
import {SubmitHandler, useForm} from "react-hook-form";
import {IAuthFormFields} from "../types";
import cn from "classnames";
type locationState = { from: string };
const SignIn = () => {
const navigate = useNavigate()
const location = useLocation();
const fromPage = (location.state as locationState)?.from ?? '/';
const {
register,
formState: { errors },
handleSubmit,
setError
} = useForm<IAuthFormFields>({
mode: 'onBlur'
});
const handleLogin: SubmitHandler<IAuthFormFields> = ({email, pass}) => {
signInWithEmailAndPassword(auth, email, pass)
.then(async (userCredential) => {
const {user} = userCredential;
await updateCurrentUser(auth, user);
navigate(fromPage);
});
}
return (
<form onSubmit={handleSubmit(handleLogin)}>
<fieldset className={"flex flex-col items-center"}>
<h1 className={"text-2xl font-medium"}>Sign In</h1>
<div className={"flex flex-col w-full my-3"}>
<input
type="email"
{...register('email', {
required: true,
pattern: {
value: /^[\w-.]+#([\w-]+\.)+[\w-]{2,4}$/,
message: 'Invalid email'
}
})}
placeholder="Email"
className={cn(styles.input, "my-3")}
/>
{errors?.email && <span className={styles.msg_error} >{errors.email.message}</span>}
<input
type="password"
{...register('pass', {
required: true,
})}
placeholder="Password"
className={cn(styles.input, "my-3")}
/>
{errors?.pass && <span className={styles.msg_error} >{errors.pass.message}</span>}
<button className={cn(styles.btn, "my-3")} >
Sign In
</button>
</div>
</fieldset>
</form>
);
};
export default SignIn;
According to documentation (https://react-hook-form.com/api/useform/handlesubmit/) - the method expects a SubmitHandler callback argument (named "a successful callback.)
((data: Object, e?: Event) => void, (errors: Object, e?: Event) => void) => Function
You have this handler, just take the event as the 2nd argument, this one:
const handleLogin: SubmitHandler<IAuthFormFields> = ({email, pass}) => {....
Will turn into this:
const handleLogin: SubmitHandler<IAuthFormFields> = ({email, pass}, e?: Event) => {
e.preventDefault()
signInWithEmailAndPassword(auth, email, pass)
.then(async (userCredential) => {....
pass e as a parameter in form onSubmit function and inside that function write
e.preventDefault();
And try to use at the begining of your handleSubmit function :
e.preventDefault()
add event.preventDefault(); on the handleLogin function :) oh and you also need a event parameter
The accepted answer types are not entirely accurate. I had some typescript errors.
This fixed it for me:
const handleLogin = (
data: dataType,
e?: React.BaseSyntheticEvent // This one specifically.
) => {
e?.preventDefault(); // Stop page refresh
console.log('debug data ->', data);
};
By calling the onSubmit event on the form like so:
<form onSubmit={handleSubmit(handleGiftCardSubmit)}>
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>
);
}}
/>
);
};
I'm trying to make the place order in the ecommerce website I'm trying to make for my personal project. I wanted after I created the data or input the data I have made I get that Id and Redirect it to the orders/[id] then the Id.
Here is my code:
import React, { useContext, useState } from "react";
import { FETCH_USER_QUERY } from "../util/graphql/Queries";
import { useMutation, useQuery } from "#apollo/react-hooks";
import { AuthContext } from "../context/auth";
import { CartContext } from "../context/cart/CartContext";
import { Form, Button } from "semantic-ui-react";
import { CREATE_ORDER_MUTATION } from "../util/graphql/Mutations";
import { useRouter } from "next/router";
export default function Checkout() {
const router = useRouter();
const [cartItems, setCartItems] = useContext(CartContext);
const [paymentMethod, setPaymentMethod] = useState("");
const [address, setAddress] = useState("");
const [createOrder, { data, loading }] = useMutation(CREATE_ORDER_MUTATION);
const qty = cartItems.map(({ quantity }) => {
return quantity;
});
const cartItemId = cartItems.map(({ id }) => {
return id;
});
function onSubmit() {
createOrder({
variables: {
qty: qty[0],
products: cartItemId[0],
paymentMethod: paymentMethod,
address: address,
},
})
.then(() => {
setTimeout(() => {
const { createOrder: order } = { ...data };
console.log(order?.id);
}, 500);
})
}
return (
<>
<Form onSubmit={onSubmit} className={loading ? "loading" : ""}>
<h2>Create a Main Category:</h2>
<Form.Field>
<Form.Input
placeholder="Please Enter Address"
name="address"
label="Address: "
onChange={(event) => {
setAddress(event.target.value);
}}
value={address}
/>
<label>Status: </label>
<select
name="category"
className="form-control"
onChange={(event) => {
setPaymentMethod(event.target.value);
}}
>
<option value="Cash On Delivery">Cash On Delivery</option>
<option value="PayPal">PayPal</option>
<option value="GCash">GCash</option>
</select>
<br />
<Button type="submit" color="teal">
Submit
</Button>
</Form.Field>
</Form>
</>
);
}
But after I submitted my inputted data the log returns me undefined, but when I input data again and submit it gives me the previous id of that data. Is there any way to do this? If you don't understand what I mean please let me know I'll explain in the comments, or if you need any code I could give it to you I will be transparent as I can
Instead of using SetTimeOut(), you can simply use the onCompleted() function in Apollo graphql to perform anything whenever the mutation is successfully completed.
const [createOrder, { data, loading }] = useMutation(CREATE_ORDER_MUTATION, {
variables: {
},
onCompleted(data) {
console.log(data) // this is log all queries from the mutation including the ID you need
// whatever you want to do when the mutation is successful
router.push({
pathname: '/any-page',
query: {CartID: data.cart_id}, // check the console.log to see how the ID is returned });
}});
You forgot to return the res parameter from your promise. It should look something like this:
.then((res) => {
setTimeout(() => {
const { createOrder: order } = { ...res };
console.log(order?.id);
}, 500);
})
The issue is being caused by stale closure. The object reference of data at the time of setTimeout being pushed into the callback queue is an older one. Hence the value was never refreshed. You need to get the newer value by dispatch the action on the same previous state or using a useRef.
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
}
})