i am working on a project and creating a form to create tours. everything is working fine just the issue is form input values are exchanging
for ex -
actual output- { tourName: 'pune darshan', location: '999', price: 'pune' }
expected output :- { tourName: 'pune darshan', location: 'pune', price: '999' }
i dont know where i am going wrong i am stuck here since 6 hrs
here is what i have tried
form component
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { createTour } from "../../store/slices/tourSlice";
import "./createListing.scss";
const CreateListing = () => {
const [tour, setTour] = useState({
tourName: "",
price: "",
location: "",
});
const dispatch = useDispatch();
const handleInput = (event) => {
setTour((tour) => ({
...tour,
[event.target.name]: event.target.value,
}));
};
const handleSubmit = (event) => {
event.preventDefault();
dispatch(createTour(tour.tourName, tour.price, tour.location));
};
return (
<div>
<div className='form-controller'>
<form action='' method='post' onSubmit={handleSubmit}>
<div className='form-group'>
<input
type='text'
className='form-control'
name='tourName'
placeholder='Enter Tour Name'
onChange={handleInput}
required
/>
</div>
<div className='form-group'>
<input
type='text'
className='form-control'
name='location'
placeholder='Enter Tour Location'
onChange={handleInput}
required
/>
</div>
<div className='form-group'>
<input
type='number'
className='form-control'
name='price'
placeholder='Enter Tour Cost'
onChange={handleInput}
required
/>
</div>
<div className='text-center'>
<button type='submit theme-btn'>Create Tour</button>
</div>
</form>
</div>
</div>
);
};
export default CreateListing;
here is the redux toolkit file
import { createSlice } from "#reduxjs/toolkit";
import axios from "axios";
import { history } from "./../../helpers/history";
export const initialState = {
tourName: "",
location: "",
price: "",
error: "",
loading: false,
};
const tourSlice = createSlice({
name: "tour",
initialState,
reducers: {
tourCreateRequest: (State, action) => {
return {
loading: true,
};
},
tourCreateSuccess: (state, action) => {
return { loading: false, tourInfo: action.payload };
},
tourCreateFail: (state, action) => {
return {
loading: false,
error: action.payload,
};
},
},
});
const {
tourCreateFail,
tourCreateRequest,
tourCreateSuccess,
} = tourSlice.actions;
export default tourSlice.reducer;
export const createTour = (tourName, location, price) => async (dispatch) => {
try {
dispatch(tourCreateRequest);
const tourData = {
tourName,
location,
price,
};
const res = await axios.post(
"http://localhost:3000/api/v1/tours",
tourData
);
if (res) {
dispatch(tourCreateSuccess);
// history.push("/dashboard");
} else {
dispatch(
tourCreateFail(
error.response && error.response.data.message
? error.response.data.message
: error.message
)
);
console.log("error");
}
} catch (error) {
dispatch(
tourCreateFail(
error.response && error.response.data.message
? error.response.data.message
: error.message
)
);
}
};
here is the model file
const mongoose = require("mongoose");
const tourSchema = mongoose.Schema(
{
tourName: { type: String },
rating: { type: String, default: 4.5 },
location: { type: String },
price: { type: String, default: 999 },
},
{ timestamps: {} }
);
const Tour = mongoose.model("Tour", tourSchema);
module.exports = Tour;
here is controller code
const createTours = async (req, res, next) => {
const { tourName, price, location } = req.body;
console.log(req.body);
try {
const newTour = new Tour({
tourName,
price,
location,
});
newTour.save();
res.status(200).json({
status: "success",
newTour,
});
} catch (error) {
res.status(404).json({
status: "failed",
error: error,
});
}
};
You pass the parameters in the createTour function in the wrong order.
You should update the dispatch line:
dispatch(createTour(tour.tourName, tour.location, tour.price));
Related
I'm trying to built a random football team picker and I'm doing the database myself.
I'm having a problem with the inputs
import React, { use, useRef, useState } from "react";
const fetchAll = async () => {
const getAll = await fetch("http://localhost:3000/api/getEquipos");
const data = await getAll.json();
return data;
};
const MyForm = () => {
const [newTeam, setNewTeam] = useState({
nombre: "",
logo: "",
liga: ""
})
const handleChange = (e) => {
setNewTeam({ [e.target.name]: e.target.value })
}
const data = use(fetchAll());
const handleSubmit = async (e) => {
e.preventDefault();
const lastId = await data.findLast((elem) => elem.id > 1);
try {
const addOne = await fetch("http://localhost:3000/api/addEquipos", {
method: "POST",
body: JSON.stringify({
nombre: newTeam.nombre,
logo: newTeam.logo,
liga: newTeam.liga,
id: lastId.id + 1,
}),
});
} catch (error) {
console.log(error);
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<input type="text"
placeholder="equipo"
name="nombre"
onChange={handleChange} />
<input type="text"
placeholder="logo"
name="logo"
onChange={handleChange} />
<input type="text"
placeholder="liga"
name="liga"
onChange={handleChange} />
<input type="submit" value="submit" />
</form>
</div>
);
};
export default MyForm;
it's a simple form for sending my fields to my database
import dbConnect from "lib/dbConnect";
import { equipoModel } from "lib/models/equiposModel";
import { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
await dbConnect();
try {
const body = JSON.parse(req.body);
console.log(body);
if (body.nombre === "") {
return;
} else {
await equipoModel.create({
nombre: body.nombre,
logo: body.logo,
liga: body.liga,
id: body.id
});
res.json({ added: true });
}
} catch (error) {
res.status(400).json(error);
}
}
and the console.log(body) shows "{ nombre: '', logo: '', liga: '', id: 3 }"
I'm trying to send the data to my database and it only shows empty strings.
It doesn't look like you're saving the Team correctly.
const handleChange = (e) => {
setNewTeam({ ...newTeam, [e.target.name]: e.target.value })
}
I recommend the new beta docs on managing state https://beta.reactjs.org/learn/managing-state
I am building a simple login form page in React. The <form> has 2 inputs (email and password), an error message, and a submit button.
The submit button can be set to the Loading state during the authentication request.
So I have the following component:
function LoginForm() {
const [state, setState] = useState({ loading: false, error: "", x: 1 }); // x value only for visualising...
const auth = useAuth();
const navigate = useNavigate();
const login = async ({ email, password }) => {
try {
if (!email || !password) {
return { errors: "Invalid fields" };
}
const { errors } = await auth.authenticate(email, password);
if (!errors) {
return {};
}
console.log("LoginButton", "Error login. Not Redirecting", errors);
return {
errors: "Por favor verifique seu email e/ou senha e tente novamente.",
};
} catch (error) {
return { errors: "Unexpected error. Please, try again later." };
}
};
const inputs = [
{
name: "email",
},
{
name: "password",
type: "password",
},
];
const handleSubmit = (values) => {
setState({ ...state, loading: true, error: "", x: 2 }); // First call
login(values).then(({ errors: error }) => {
if (!error) navigate("/profile");
const newState = { loading: false, error: "Error while login", x: 3 }; // Second call
setState(newState);
});
};
useEffect(() => {
console.log(state); // Only for debugin
});
return (
<Form
inputs={inputs}
onSubmit={handleSubmit}
>
<ErrorMessage text={state.error} />
<div>
<Submit loading={state.loading}>Entrar</Submit>
<Link
to="/forgot-password"
>
Esqueceu sua senha?
</Link>
</div>
</Form>
);
}
The <Form/> component only gets the inputs array and creates the list of inputs...
The login function was called, and it set the state successfully on the first setState call (x: 2), but on the second call, the state was reset to the default value (x: 1).
Why did the second setState reset the default value? How can I fix this?
I think I've solved... But I don't Understand how...
function LoginForm() {
const [state, setState] = useState({ loading: false, error: "", x: 1 });
const auth = useAuth();
const navigate = useNavigate();
const inputs = [
{
name: "email",
},
{
name: "password",
type: "password",
},
];
const handleSubmit = async (values) => {
const { email, password } = values;
setState({ ...state, loading: true, error: "", x: 2 });
auth.authenticate(email, password).then(({ errors }) => {
if (!errors) navigate("/profile");
const newState = { loading: false, error: errors, x: 3 };
setState(newState);
});
};
useEffect(() => {
console.log(state);
});
return (
<Form
inputs={inputs}
onSubmit={handleSubmit}
>
<ErrorMessage text={state.error} />
<div>
<Submit loading={state.loading}>Entrar</Submit>
<Link
css={`
color: white;
`}
to="/forgot-password"
>
Esqueceu sua senha?
</Link>
</div>
</Form>
);
}
export default LoginForm;
This worked...
I'm getting troubles about showing product, when I try to GET api/products/:id, my browser return localhost:3000/produit/undefined, and Request failed with status code 500
link repo : https://github.com/Kwonsongji/mern-stack-calone-shop
Here is my part Back :
Model : Product.js
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
image: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
material: {
type: String,
required: true
},
countInStock: {
type: Number,
required: true
},
}, {
timestamps: true
})
const Product = mongoose.model('Product', productSchema);
module.exports = Product;
Controller : productCtrl.js
const db = require('../config/db');
const Product = require('../models/Product');
const datas = require('../data/datas');
const productCtrl = {
sendDatasProductToMoongoose: async (req, res) => {
try {
//await db.products.dropIndexes({})
await Product.deleteMany({})
//.remove({});
const createdProducts = await Product.insertMany(datas.products);
console.log('Data Import Sucess createdProducts',createdProducts);
res.send({ createdProducts});
} catch (error) {
res.status(500).send(error.message)
}
},
getAllProducts: async (req, res) => {
try {
console.log("req :", req.body);
// Product.find({})
const products = await Product.find()
.then((Document) => {
console.log("Document: ", Document);
console.log("Req.params: ", req.params);
if (!Document) {
return res
.status(404)
.json({ message: "This ressource doesn't exist " });
}
res.status(200).json(datas.products);
})
.catch((error) => {
console.log(error);
res.status(500).json(error)
});
} catch (error) {
console.error(error);
res.status(500).json({ message: "Server Error" });
}
},
getProductById: async (req, res) => {
try {
/* const product = await Product.findOne(_id) */
const product = await Product.findById(req.params.id)
/* .then((Document) => {
if (!Document) {
return res
.status(404)
.json({ message: "This document doesn't exist" });
}
res.status(200).json(Document);
})
.catch((error) => {
res.status(500).json(error);
}); */
res.send(product); // returne moi les données en json
} catch (error) {
console.error(error);
res.status(500).json({ message: "Server Error" });
}
},
};
module.exports = productCtrl;
Router : product.js
const productCtrl = require('../controllers/productCtrl');
const router = express.Router();
router.get('/seed', productCtrl.sendDatasProductToMoongoose )
router.get('/', productCtrl.getAllProducts);
router.get('/:id', productCtrl.getProductById);
module.exports = router;
Server : server.js
const express = require("express");
const cors = require("cors");
// require connexion db and run it
const connectDB = require("./config/db");
const app = express();
connectDB();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
app.use("/api/products",require("./routes/product.js"));
app.use("/api/users",require("./routes/user.js"))
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on ${PORT}`));
Frontend :
productActions :
import * as actionTypes from '../constants/productConstants';
import axios from 'axios';
export const getProducts = () => async (dispatch) => {
try {
dispatch({ type: actionTypes.GET_PRODUCTS_REQUEST });
const { data } = await axios.get(`/api/products`);
dispatch({
type: actionTypes.GET_PRODUCTS_SUCESS,
payload: data
})
} catch (error) {
dispatch({
type: actionTypes.GET_PRODUCTS_FAIL,
payload: error.response && error.response.message
? error.response.data.message
: error.message
});
}
};
export const getProductDetails = (id) => async (dispatch) => {
try {
dispatch({ type: actionTypes.GET_PRODUCT_DETAILS_REQUEST });
const { data } = await axios.get(`/api/products/${id}`);
dispatch({
type: actionTypes.GET_PRODUCT_DETAILS_SUCESS,
payload: data
})
} catch (error) {
dispatch({
type: actionTypes.GET_PRODUCT_DETAILS_FAIL,
payload: error.response && error.response.message
? error.response.data.message
: error.message
});
}
};
export const removeProductDetails = () => (dispatch) => {
dispatch({ type: actionTypes.GET_PRODUCT_DETAILS_RESET });
};
productReducer.js:
import * as actionTypes from '../constants/productConstants';
const initialStateForGetProducts = {
products: []
};
export const getProductsReducer = (state = initialStateForGetProducts, action) => {
switch (action.type) {
case actionTypes.GET_PRODUCTS_REQUEST:
return {
loading: true,
products:[]
}
case actionTypes.GET_PRODUCTS_SUCESS:
return {
loading: false,
products: action.payload
}
case actionTypes.GET_PRODUCTS_FAIL:
return {
loading: false,
error: action.payload //on veut afficher l'erreur
}
default:
return state;
}
}
const initialStateForGetOneProduct = {
product: {}
}
export const getProductDetailsReducer = (state = initialStateForGetOneProduct, action) => {
switch (action.type) {
case actionTypes.GET_PRODUCT_DETAILS_REQUEST:
return {
loading: true,
}
case actionTypes.GET_PRODUCT_DETAILS_SUCESS:
return {
loading: false,
product: action.payload
}
case actionTypes.GET_PRODUCT_DETAILS_FAIL:
return {
loading: false,
error: action.payload
}
case actionTypes.GET_PRODUCT_DETAILS_RESET:
return {
product: {}
}
default:
return state;
}
}
ProductScreen.js
/* eslint-disable react/prop-types */
import React from 'react';
import './style.scss';
//import moonPendant from '../../assets/fonts/moon-pendant.png';
import { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
// Actions
import { getProductDetails } from '../../../redux/actions/productActions';
import { addToCart } from '../../../redux/actions/cartActions';
const ProductScreen = ( {match, history}) => {
const [qty, setQty] = useState(1);
const dispatch = useDispatch();
const productDetails = useSelector(state => state.getProductDetails);
const { loading, error, product } = productDetails;
useEffect(() => {
if (product && match.params.id !== product._id) {
dispatch(getProductDetails(match.params.id));
}
}, [dispatch, product, match]);
const addToCartHandler = () => {
dispatch(addToCart(product._id, qty));
history.push("/panier");
}
return (
<div className="productScreen">
{ loading ? <h2>Loading...</h2>
: error ? <h2>{error}</h2>
: (
<>
<div className="productScreen__left">
<div className="productScreen__left__image">
{/* <img src={moonPendant} alt={product.name} /> */}
<img src={product.image} alt={product.name} />
</div>
<div className="productScreen__left__info">
<p className="productScreen__left__name"> {product.name}</p>
<p className="productScreen__left__price"> {product.price}</p>
<p className="productScreen__left__material"> {product.material}</p>
</div>
</div>
<div className="productScreen__right">
<div className="productScreen__right__info">
<p className="productScreen__right__price">
Prix: <span> {product.price} euros </span>
</p>
<p className="productScreen__right__status">
Statut:
<span>
{product.countInStock > 0 ? "En Stock" : "En Rupture de Stock"}
</span>
</p>
<p className="productScreen__right__qty">
Quantité:
<select value={qty} /* lorsqu'on slct le nbre voulu */
onChange={(e) => setQty(e.target.value)}>
{[...Array(product.countInStock).keys()].map((x) => (
<option
key={x+1}
value={x+1}
>
{x+1}
</option> //on utilise l'instance array pour créer un tableau de liste
))}
</select>
</p>
<p>
<button
type="button"
onClick={ addToCartHandler}
className="productScreen__right__add"
> Ajouter
</button>
</p>
</div>
</div>
</>
)}
</div>
)
}
export default ProductScreen;
link repo : https://github.com/Kwonsongji/mern-stack-calone-shop
I'm pretty new to javascript, and I've been following a tutorial to create an e-commerce website. For some reason I am now getting a TypeError: Cannot read property 'category' of undefined error in reference to:
export const listProducts = ({category = '' }) => async (dispatch)
part of my productAction.js document. I've included the rest of my productAction.js document below, as well as the other important documents. I would really appreciate any help or guidance on this issue.
productAction.js
import Axios from 'axios';
import {
PRODUCT_CREATE_FAIL,
PRODUCT_CREATE_REQUEST,
PRODUCT_CREATE_SUCCESS,
PRODUCT_DETAILS_FAIL,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_LIST_FAIL,
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
PRODUCT_UPDATE_REQUEST,
PRODUCT_UPDATE_SUCCESS,
PRODUCT_UPDATE_FAIL,
PRODUCT_DELETE_REQUEST,
PRODUCT_DELETE_FAIL,
PRODUCT_DELETE_SUCCESS,
PRODUCT_REVIEW_CREATE_REQUEST,
PRODUCT_REVIEW_CREATE_SUCCESS,
PRODUCT_REVIEW_CREATE_FAIL,
PRODUCT_CATEGORY_LIST_SUCCESS,
PRODUCT_CATEGORY_LIST_REQUEST,
PRODUCT_CATEGORY_LIST_FAIL,
/*PRODUCT_SAVE_REQUEST,*/
} from '../constants/productConstants';
export const listProducts = ({name = '', category = '' }) => async (dispatch) => {
dispatch({
type: PRODUCT_LIST_REQUEST,
});
try {
const { data } = await Axios.get(`/api/products?name=${name}category=${category}`);
dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: PRODUCT_LIST_FAIL, payload: error.message });
}
};
export const listProductCategories = () => async (dispatch) => {
dispatch({
type: PRODUCT_CATEGORY_LIST_REQUEST,
});
try {
const { data } = await Axios.get(`/api/products/categories`);
dispatch({ type: PRODUCT_CATEGORY_LIST_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: PRODUCT_CATEGORY_LIST_FAIL, payload: error.message });
}
};
export const detailsProduct = (productId) => async (dispatch) => {
dispatch({ type: PRODUCT_DETAILS_REQUEST, payload: productId });
try {
const { data } = await Axios.get(`/api/products/${productId}`);
dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: data });
} catch (error) {
dispatch({
type: PRODUCT_DETAILS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
export const createProduct = () => async (dispatch, getState) => {
dispatch({ type: PRODUCT_CREATE_REQUEST });
const {
userSignin: { userInfo },
} = getState();
try {
const { data } = await Axios.post(
'/api/products',
{},
{
headers: { Authorization: `Bearer ${userInfo.token}` },
}
);
dispatch({
type: PRODUCT_CREATE_SUCCESS,
payload: data.product,
});
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
dispatch({ type: PRODUCT_CREATE_FAIL, payload: message });
}
};
export const updateProduct = (product) => async (dispatch, getState) => {
dispatch({ type: PRODUCT_UPDATE_REQUEST, payload: product });
const {
userSignin: { userInfo },
} = getState();
try {
const { data } = await Axios.put(`/api/products/${product._id}`, product, {
headers: { Authorization: `Bearer ${userInfo.token}` },
});
dispatch({ type: PRODUCT_UPDATE_SUCCESS, payload: data });
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
dispatch({ type: PRODUCT_UPDATE_FAIL, error: message });
}
};
export const deleteProduct = (productId) => async (dispatch, getState) => {
dispatch({ type: PRODUCT_DELETE_REQUEST, payload: productId });
const {
userSignin: { userInfo },
} = getState();
try {
const {data} = Axios.delete(`/api/products/${productId}`, {
headers: { Authorization: `Bearer ${userInfo.token}` },
});
dispatch({ type: PRODUCT_DELETE_SUCCESS });
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
dispatch({ type: PRODUCT_DELETE_FAIL, payload: message });
}
};
export const createReview = (productId, review) => async (
dispatch,
getState
) => {
dispatch({ type: PRODUCT_REVIEW_CREATE_REQUEST });
const {
userSignin: { userInfo },
} = getState();
try {
const { data } = await Axios.post(
`/api/products/${productId}/reviews`,
review,
{
headers: { Authorization: `Bearer ${userInfo.token}` },
}
);
dispatch({
type: PRODUCT_REVIEW_CREATE_SUCCESS,
payload: data.review,
});
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
dispatch({ type: PRODUCT_REVIEW_CREATE_FAIL, payload: message });
}
};
productReducer.js
const {
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
PRODUCT_LIST_FAIL,
PRODUCT_CATEGORY_LIST_REQUEST,
PRODUCT_CATEGORY_LIST_SUCCESS,
PRODUCT_CATEGORY_LIST_FAIL,
} = require('../constants/productConstants');
export const productListReducer = (
state = { loading: true, products: [] },
action
) => {
switch (action.type) {
case PRODUCT_LIST_REQUEST:
return { loading: true };
case PRODUCT_LIST_SUCCESS:
return { loading: false, products: action.payload };
case PRODUCT_LIST_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
export const productCategoryListReducer = (
state = { loading: true, products: [] },
action
) => {
switch (action.type) {
case PRODUCT_CATEGORY_LIST_REQUEST:
return { loading: true };
case PRODUCT_CATEGORY_LIST_SUCCESS:
return { loading: false, categories: action.payload };
case PRODUCT_CATEGORY_LIST_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
Backend
productModel.js
import mongoose from 'mongoose';
const reviewSchema = new mongoose.Schema(
{
name: { type: String, required: true },
comment: { type: String, required: true },
rating: { type: Number, required: true },
},
{
timestamps: true,
}
);
const productSchema = new mongoose.Schema(
{
name: { type: String, required: true, unique: true },
image: { type: String, required: true },
brand: { type: String, required: true },
category: { type: String, required: true },
description: { type: String, required: true },
price: { type: Number, required: true },
countInStock: { type: Number, required: true },
rating: { type: Number, required: true },
numReviews: { type: Number, required: true },
reviews: [reviewSchema],
},
{
timestamps: true,
}
);
const Product = mongoose.model('Product', productSchema);
export default Product;
productRouter.js
import express from 'express';
import expressAsyncHandler from 'express-async-handler';
import data from '../data.js';
import Product from '../models/productModel.js';
import { isAdmin, isAuth } from '../utils.js';
const productRouter = express.Router();
productRouter.get(
'/',
expressAsyncHandler(async (req, res) => {
const name = req.query.name || '';
const category = req.query.category || '';
const nameFilter = name ? { name: { $regex: name, $options: 'i' } } : {};
const categoryFilter = category ? { category } : {};
const products = await Product.find({
...nameFilter,
...categoryFilter,
}).res.send(products);
})
);
productRouter.get(
'/categories',
expressAsyncHandler(async (req, res) => {
const categories = await Product.find().distinct('category');
res.send(categories);
})
);
productRouter.get(
'/seed',
expressAsyncHandler(async (req, res) => {
// await Product.remove({});
const createdProducts = await Product.insertMany(data.products);
res.send({ createdProducts });
})
);
productRouter.get(
'/:id',
expressAsyncHandler(async (req, res) => {
const product = await Product.findById(req.params.id);
if (product) {
res.send(product);
} else {
res.status(404).send({ message: 'Product Not Found' });
}
})
);
productRouter.post(
'/',
isAuth,
isAdmin,
expressAsyncHandler(async (req, res) => {
const product = new Product({
name: 'sample name ' + Date.now(),
image: '/images/p1.jpg',
price: 0,
category: 1,
brand: 'sample brand',
countInStock: 0,
rating: 0,
numReviews: 0,
description: 'sample description',
});
const createdProduct = await product.save();
res.send({ message: 'Product Created', product: createdProduct });
})
);
productRouter.put(
'/:id',
isAuth,
isAdmin,
expressAsyncHandler(async (req, res) => {
const productId = req.params.id;
const product = await Product.findById(productId);
if (product) {
product.name = req.body.name;
product.price = req.body.price;
product.image = req.body.image;
product.category = req.body.category;
product.brand = req.body.brand;
product.countInStock = req.body.countInStock;
product.description = req.body.description;
const updatedProduct = await product.save();
res.send({ message: 'Product Updated', product: updatedProduct });
} else {
res.status(404).send({ message: 'Product Not Found' });
}
})
);
export default productRouter;
Update:
So, I think the issue was my listProducts constant. In my HomeScreen.js document (not previously included), I realized that I had forgot to add {} within dispatch(listProducts());
useEffect(() =>{
dispatch(listProducts({}));
}, [dispatch]);
My only issue is that while my page now loads, I now have a Request failed with status code 500 error.
I've included the rest of my HomeScreen.js document and my other documents that include listProducts. Any additional guidance would be greatly appreciated.
HomeScreen.js
import React, { useEffect } from 'react';
import Product from '../components/Product';
import LoadingBox from '../components/LoadingBox';
import MessageBox from '../components/MessageBox';
import { useDispatch, useSelector } from 'react-redux';
import { listProducts } from '../actions/productActions';
import { Link } from 'react-router-dom';
export default function HomeScreen() {
const dispatch = useDispatch();
const productList = useSelector((state) => state.productList);
const { loading, error, products } = productList;
useEffect(() =>{
dispatch(listProducts({}));
}, [dispatch]);
return (
<div>
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
<>
{products.length === 0 && <MessageBox>No Product Found</MessageBox>}
<div className="row center">
{products.map((product) => (
<Product key={product._id} product={product}></Product>
))}
</div>
</>
)}
</div>
);
}
SearchScreen.js
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useParams } from 'react-router-dom';
import { listProducts } from '../actions/productActions';
import LoadingBox from '../components/LoadingBox';
import MessageBox from '../components/MessageBox';
import Product from '../components/Product';
export default function SearchScreen(props) {
const { name = 'all', category = 'all' } = useParams();
const dispatch = useDispatch();
const productList = useSelector((state) => state.productList);
const { loading, error, products } = productList;
const productCategoryList = useSelector((state) => state.productCategoryList);
const {
loading: loadingCategories,
error: errorCategories,
categories,
} = productCategoryList;
useEffect(() => {
dispatch(
listProducts({
name: name !== 'all' ? name : '',
category: category !== 'all' ? category : '',
})
);
}, [category, dispatch, name]);
const getFilterUrl = (filter) => {
const filterCategory = filter.category || category;
const filterName = filter.name || name;
return `/search/category/${filterCategory}/name/${filterName}`;
};
return (
<div>
<div className="row">
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
<div>{products.length} Results</div>
)}
</div>
<div className="row top">
<div className="col-1">
<h3>Department</h3>
{loadingCategories ? (
<LoadingBox></LoadingBox>
) : errorCategories ? (
<MessageBox variant="danger">{errorCategories}</MessageBox>
) : (
<ul>
{categories.map((c) => (
<li key={c}>
<Link
className={c === category ? 'active' : ''}
to={getFilterUrl({ category: c })}
>
{c}
</Link>
</li>
))}
</ul>
)}
</div>
<div className="col-3">
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
<>
{products.length === 0 && (
<MessageBox>No Product Found</MessageBox>
)}
<div className="row center">
{products.map((product) => (
<Product key={product._id} product={product}></Product>
))}
</div>
</>
)}
</div>
</div>
</div>
);
}
ProductListScreen.js
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
createProduct,
deleteProduct,
listProducts,
} from '../actions/productActions';
import LoadingBox from '../components/LoadingBox';
import MessageBox from '../components/MessageBox';
import {
PRODUCT_CREATE_RESET,
PRODUCT_DELETE_RESET,
} from '../constants/productConstants';
export default function ProductListScreen(props) {
const productList = useSelector((state) => state.productList);
const { loading, error, products } = productList;
const productCreate = useSelector((state) => state.productCreate);
const {
loading: loadingCreate,
error: errorCreate,
success: successCreate,
product: createdProduct,
} = productCreate;
const productDelete = useSelector((state) => state.productDelete);
const {
loading: loadingDelete,
error: errorDelete,
success: successDelete,
} = productDelete;
const dispatch = useDispatch();
useEffect(() => {
if (successCreate) {
dispatch({ type: PRODUCT_CREATE_RESET });
props.history.push(`/product/${createdProduct._id}/edit`);
}
if (successDelete) {
dispatch({ type: PRODUCT_DELETE_RESET });
}
dispatch(listProducts());
/// TODO: dispatch delete action
}, [createdProduct, dispatch, props.history, successCreate, successDelete]);
const deleteHandler = (product) => {
if (window.confirm('Are you sure to delete?')) {
dispatch(deleteProduct(product._id));
}
};
const createHandler = () => {
dispatch(createProduct());
};
return (
<div>
<div className="row">
<h1>Products</h1>
<button type="button" className="primary" onClick={createHandler}>
Create Product
</button>
</div>
{loadingDelete && <LoadingBox></LoadingBox>}
{errorDelete && <MessageBox variant="danger">{errorDelete}</MessageBox>}
{loadingCreate && <LoadingBox></LoadingBox>}
{errorCreate && <MessageBox variant="danger">{errorCreate}</MessageBox>}
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
<table className="table">
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>PRICE</th>
<th>CATEGORY</th>
<th>BRAND</th>
<th>ACTIONS</th>
</tr>
</thead>
<tbody>
{products.map((product) => (
<tr key={product._id}>
<td>{product._id}</td>
<td>{product.name}</td>
<td>{product.price}</td>
<td>{product.category}</td>
<td>{product.brand}</td>
<td>
<button
type="button"
className="small"
onClick={() =>
props.history.push(`/product/${product._id}/edit`)
}
>
Edit
</button>
<button
type="button"
className="small"
onClick={() => deleteHandler(product)}
>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
}
Based on the error it seems you may be calling listProducts and not passing any argument. If this is expected then you can provide an initial value for the argument to destructure category from.
export const listProducts = ({ category = '' } = {}) => async (dispatch) => {
dispatch({
type: PRODUCT_LIST_REQUEST,
});
try {
const { data } = await Axios.get(`/api/products?category=${category}`);
dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: PRODUCT_LIST_FAIL, payload: error.message });
}
};
SO the most plausible answer according to me is (based on the error provided),
The error is causing because you are not passing anything. Due to which the javascript is trying to spread nothing and giving you the error "Cannot read property 'category' of undefined".
I know this is a bit confusing let me explain it with an example
let's say you have a function
const test = ({context = ""}) => {
console.log(context)
}
and if you call the test function without passing an object like below
test();
this will produce the error "Cannot read property 'category' of undefined" as the function definition is trying to spread an object and get a context property out of that object but you are passing nothing.
instead, if you call the function
test({});
or
test({context=1});
this will not cause you any error as function definition will get an object to spread.
for more details on object spread syntax, you can refer MDN docs here
Hope its helpful.
I am using react hooks forms, and I am trying to set the default values of a form that is outputted by mapping over an array and outputting the inputs in the form. I have reduced the array to an object like this {name0:"fijs",name1:"3838"...} and if I manually pass that in the default values it maps to my inputs and populates them. However if I enter them from the variable that is doing the reduce function it doesn't populate it. I think it is because on first render it is undefined. I have tried using a useEffect, but that didn't work so I am stuck.
This is the part of the code I am working on
const test = formState?.reduce((obj, item, idx) => {
return { ...obj, [`${item.name}${idx}`]: "fdsjfs" };
}, {});
const { register, handleSubmit, errors } = useForm({
defaultValues: test,
});
console.log(test);
and this is the whole thing
import { useQuery, gql, useMutation } from "#apollo/client";
import { useEffect, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { useForm } from "react-hook-form";
const INPUT_VALUES = gql`
query GetInputValues {
allFormInputVals {
data {
name
_id
type
}
}
}
`;
const ADD_INPUT_VALUES = gql`
mutation AddInputValues(
$name: String!
$type: String!
$index: Int!
$ID: ID!
) {
createFormInputVal(
data: {
name: $name
type: $type
index: $index
formRoot: { connect: $ID }
}
) {
name
}
}
`;
const Home = () => {
const blankFormInput = {
__typename: "FormInputVal",
name: "test",
_id: uuidv4(),
type: "text",
};
const [formState, setFormState] = useState([blankFormInput]);
const [formStateVals, setFormStateVals] = useState(undefined);
const { loading, error, data } = useQuery(INPUT_VALUES);
const [createFormInputVal, { data: createInputData }] = useMutation(
ADD_INPUT_VALUES
);
useEffect(() => {
setFormState(data?.allFormInputVals?.data);
}, [data]);
const test = formState?.reduce((obj, item, idx) => {
return { ...obj, [`${item.name}${idx}`]: "fdsjfs" };
}, {});
const { register, handleSubmit, errors } = useForm({
defaultValues: test,
});
console.log(test);
const onSubmit = (data) => console.log(data);
console.log(errors);
const addInput = async () => {
const blanktext = {
__typename: "FormInputVal",
name: "Product Image",
_id: uuidv4(),
type: "text",
};
setFormState([...formState, { ...blanktext }]);
console.log(formState);
const res = await createFormInputVal({
variables: {
name: "test",
type: "text",
index: 0,
ID: "291541554941657608",
},
}).catch(console.error);
console.log(res);
};
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<input type="button" value="Add Form Input" onClick={addInput} />
{formState?.map((val, idx) => {
const nameId = `name${idx}`;
const typeId = `type-${idx}`;
return (
<div key={val._id}>
{val.type === "text" && (
<>
<label htmlFor={nameId}>{`${val.name} #${idx + 1}`}</label>
<input
type="text"
name={nameId}
id={nameId}
className={val.type}
ref={register()}
/>
{/* <label htmlFor={typeId}>{`Type #${idx + 1}`}</label>
<select name={typeId} id={typeId} className={val.type}>
{data.allFormInputVals.data.map((item) => {
return (
<option key={item._id} value={item.type}>
{item.type}
</option>
);
})}
</select> */}
</>
)}
</div>
);
})}
<button type="submit">Save Form</button>
</form>
</>
);
};
export default Home;
UPDATE: I have tried useEffect with a reset from the api, I thought this was the solution, but still no dice.
const { register, handleSubmit, errors, reset } = useForm();
useEffect(() => {
const result = test; // result: { firstName: 'test', lastName: 'test2' }
reset(result); // asynchronously reset your form values
}, [reset]);
UPDATE: I abstracted the Form to it;s own component, but it still does not work.
Form.js
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { useQuery, gql, useMutation } from "#apollo/client";
import { v4 as uuidv4 } from "uuid";
const ADD_INPUT_VALUES = gql`
mutation AddInputValues(
$name: String!
$type: String!
$index: Int!
$ID: ID!
) {
createFormInputVal(
data: {
name: $name
type: $type
index: $index
formRoot: { connect: $ID }
}
) {
name
}
}
`;
export default function Form({ formState, setFormState }) {
const test = formState?.reduce((obj, item, idx) => {
return { ...obj, [`${item.name}${idx}`]: "fdsjfs" };
}, {});
console.log(test);
const { register, handleSubmit, errors } = useForm({ defaultValues: test });
const [formStateVals, setFormStateVals] = useState(undefined);
// console.log(test);
const onSubmit = (data) => console.log(data);
console.log(errors);
const addInput = async () => {
const blanktext = {
__typename: "FormInputVal",
name: "Product Image",
_id: uuidv4(),
type: "text",
};
setFormState([...formState, { ...blanktext }]);
console.log(formState);
const res = await createFormInputVal({
variables: {
name: "test",
type: "text",
index: 0,
ID: "291541554941657608",
},
}).catch(console.error);
console.log(res);
};
const [createFormInputVal, { data: createInputData }] = useMutation(
ADD_INPUT_VALUES
);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="button" value="Add Form Input" onClick={addInput} />
{formState?.map((val, idx) => {
const nameId = `name${idx}`;
const typeId = `type-${idx}`;
return (
<div key={val._id}>
{val.type === "text" && (
<>
<label htmlFor={nameId}>{`${val.name} #${idx + 1}`}</label>
<input
type="text"
name={nameId}
id={nameId}
className={val.type}
ref={register()}
/>
{/* <label htmlFor={typeId}>{`Type #${idx + 1}`}</label>
<select name={typeId} id={typeId} className={val.type}>
{data.allFormInputVals.data.map((item) => {
return (
<option key={item._id} value={item.type}>
{item.type}
</option>
);
})}
</select> */}
</>
)}
</div>
);
})}
<button type="submit">Save Form</button>
</form>
);
}
index.js
import { useQuery, gql, useMutation } from "#apollo/client";
import { useEffect, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import Form from "../components/Form";
const INPUT_VALUES = gql`
query GetInputValues {
allFormInputVals {
data {
name
_id
type
}
}
}
`;
const Home = () => {
const blankFormInput = {
__typename: "FormInputVal",
name: "test",
_id: uuidv4(),
type: "text",
};
const [formState, setFormState] = useState([blankFormInput]);
const { loading, error, data } = useQuery(INPUT_VALUES);
useEffect(() => {
const formData = data?.allFormInputVals?.data;
setFormState(formData);
}, [data]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<>
<Form formState={formState} setFormState={setFormState} />
</>
);
};
export default Home;
You could extract the form to its own component and only render it when the data is fetched. This way, when you use useForm in the child component, the default values will be set properly.
const Home = () => {
const { loading, error, data } = useQuery(INPUT_VALUES)
const blankFormInput = {
__typename: "FormInputVal",
name: "test",
_id: uuidv4(),
type: "text",
}
const [formState, setFormState] = useState([blankFormInput])
// other code
if (loading) {
return <p>Loading...</p>
}
return <MyForm defaultValues={formState} />
}
If you don't want to change the structure, you could set the input values using setValue when the data is ready.
useEffect(() => {
const formData = data?.allFormInputVals?.data
setFormState(formData)
formData?.forEach((item, idx) => {
setValue(`${item.name}${idx}`, 'whatever')
})
}, [data])