I’m working on a podcast app that has an e-commerce section for which I am following this tutorial: https://www.youtube.com/watch?v=CDtPMR5y0QU
I’ve been getting the following error from the network tab when I click the Place Order button:
message: "Order validation failed: orderItems.0.product: Cast to ObjectId failed for value "1" (type string) at path "product", orderItems.0._id: Cast to ObjectId failed for value "1" (type string) at path "_id", orderItems.0.image: Path `image` is required., orderItems.1.image: Path `image` is required."
In the console, there is this error:
POST http://localhost:3000/api/orders 500 (Internal Server Error)
I think this has to do with the images not showing up from the backend…it just has the icon for when a photo does not show up. Could it be related to the folder structure? I’ve followed the code in the video exactly. This error came after implementing Video 28.
Screenshot of the folder structure:
folder-structure
I added two “images” folders because they weren’t showing up and I wanted to test them in the src and public folders. It didn’t work for either.
Here is my code:
BACKEND
This is the data that is fed into the MongoDB models
users: [
{
name: "example",
email: "[email protected]",
password: bcrypt.hashSync("123456"),
isAdmin: true,
},
{
name: "example",
email: "[email protected]",
password: bcrypt.hashSync("123456"),
isAdmin: false,
},
],
products: [
{
name: "LTBT Sweater",
slug: "ltbt-sweater",
price: 70,
image: "/images/1.png",
category: "Hoodies",
countInStock: 10,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
},
{
name: "LTBT Keychain",
slug: "ltbt-keychain",
price: 50,
image: "/images/1.png",
category: "Accessories",
countInStock: 9,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
},
{
name: "LTBT Sticker",
slug: "ltbt-sticker",
price: 20,
image: "/images/1.png",
category: "Stickers",
countInStock: 17,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
},
],
};
export default productData;
This is the db model:
import mongoose from "mongoose";
const orderSchema = new mongoose.Schema(
{
orderItems: [
{
slug: { type: String, required: true },
name: { type: String, required: true },
quantity: { type: Number, required: true },
image: { type: String, required: true },
price: { type: Number, required: true },
product: {
type: mongoose.Schema.Types.ObjectId,
ref: "Product",
required: true,
},
},
],
shippingAddress: {
fullName: { type: String, required: true },
address: { type: String, required: true },
city: { type: String, required: true },
postalCode: { type: String, required: true },
country: { type: String, required: true },
},
paymentMethod: { type: String, required: true },
paymentResult: {
id: String,
status: String,
update_time: String,
email_address: String,
},
itemsPrice: { type: Number, required: true },
shippingPrice: { type: Number, required: true },
taxPrice: { type: Number, required: true },
totalPrice: { type: Number, required: true },
user: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
isPaid: { type: Boolean, default: false },
paidAt: { type: Date },
isDelivered: { type: Boolean, default: false },
deliveredAt: { type: Date },
},
{
timestamps: true,
}
);
const Order = mongoose.model("Order", orderSchema);
export default Order;
This is the orderRoutes file
import express from "express";
import Order from "../models/orderModel.js";
import expressAsyncHandler from "express-async-handler";
import { isAuth } from "../utils.js";
const orderRouter = express.Router();
orderRouter.post(
"/",
isAuth,
expressAsyncHandler(async (req, res) => {
const newOrder = new Order({
orderItems: req.body.orderItems.map((x) => ({ ...x, product: x._id })),
shippingAddress: req.body.shippingAddress,
paymentMethod: req.body.paymentMethod,
itemsPrice: req.body.itemsPrice,
shippingPrice: req.body.shippingPrice,
taxPrice: req.body.taxPrice,
totalPrice: req.body.totalPrice,
user: req.user._id,
});
const order = await newOrder.save();
res.status(201).send({ message: "New Order Created", order });
})
);
export default orderRouter;
This is the server.js file
import cors from "cors";
import mongoose from "mongoose";
import dotenv from "dotenv";
import seedRouter from "./routes/seedRoutes.js";
import productRouter from "./routes/productRoutes.js";
import userRouter from "./routes/userRoutes.js";
import orderRouter from "./routes/orderRoutes.js";
dotenv.config();
mongoose
.connect(process.env.MONGODB_URI)
.then(() => {
console.log("Connected to DB");
})
.catch((err) => {
console.log(err.message);
});
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
app.use("/api/seed", seedRouter);
app.use("/api/orders", orderRouter);
app.use("/api/store", productRouter);
app.use("/api/users", userRouter);
app.use((err, req, res, next) => {
res.status(500).send({ message: err.message });
});
const port = process.env.PORT || 5000;
app.listen(port, () => {
console.log(`SERVER RUNNING ON PORT ${port}`);
});
FRONTEND
Here is the page that has the button (Place Order button)
import CheckoutSteps from "../components/CheckoutSteps";
import { Helmet } from "react-helmet-async";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";
import ListGroup from "react-bootstrap/ListGroup";
import { Link, useNavigate } from "react-router-dom";
import { useContext, useEffect, useReducer } from "react";
import { Store } from "../Store";
import { toast } from "react-toastify";
import { getError } from "../utils";
import axios from "axios";
import LoadingBox from "../components/LoadingBox";
//reducer for creating an order
const reducer = (state, action) => {
switch (action.type) {
case "CREATE_REQUEST":
return { ...state, loading: true };
case "CREATE_SUCCESS":
return { ...state, loading: false };
case "CREATE_FAIL":
return { ...state, loading: false };
default:
return state;
}
};
export default function PlaceOrder() {
const navigate = useNavigate();
const { state, dispatch: ctxDispatch } = useContext(Store);
const { cart, userInfo } = state;
const [{ loading }, dispatch] = useReducer(reducer, {
loading: false,
});
//round to 2 decimal places
const round = (num) => Math.round(num * 100 + Number.EPSILON) / 100;
//calculate items price
cart.itemsPrice = round(
cart.cartItems.reduce((a, c) => a + c.quantity * c.price, 0)
);
//if total price is more than 100, make shipping price 15 otherwise it is 10
cart.shippingPrice = cart.itemsPrice > 100 ? round(10) : round(15);
//tax is 13%
cart.taxPrice = round(0.13 * cart.itemsPrice);
//total price
cart.totalPrice = cart.itemsPrice + cart.shippingPrice + cart.taxPrice;
const placeOrderHandler = async () => {
try {
dispatch({ type: "CREATE_REQUEST" });
const { data } = await axios.post(
"/api/orders",
{
orderItems: cart.cartItems,
shippingAddress: cart.shippingAddress,
paymentMethod: cart.paymentMethod,
itemsPrice: cart.itemsPrice,
shippingPrice: cart.shippingPrice,
taxPrice: cart.taxPrice,
totalPrice: cart.totalPrice,
},
{
headers: {
authorization: `Bearer ${userInfo.token}`,
},
}
);
ctxDispatch({ type: "CART_CLEAR" });
dispatch({ type: "CREATE_SUCCESS" });
localStorage.removeItem("cartItems");
navigate(`/order/${data.order._id}`);
} catch (err) {
dispatch({ type: "CREATE_FAIL" });
toast.error(getError(err));
}
};
useEffect(() => {
if (!cart.paymentMethod) {
navigate("/payment");
}
}, [cart, navigate]);
return (
<div>
<CheckoutSteps step1 step2 step3 step4></CheckoutSteps>
<Helmet>
<title>LTBT | Preview Order</title>
</Helmet>
<h1 className="my-3">Preview Order</h1>
<Row>
<Col md={8}>
<Card className="mb-3">
<Card.Body>
<Card.Title>Shipping</Card.Title>
<Card.Text>
<strong>Name:</strong> {cart.shippingAddress.fullName} <br />
<strong>Address: </strong> {cart.shippingAddress.address},
{cart.shippingAddress.city}, {cart.shippingAddress.postalCode},
{cart.shippingAddress.country}
</Card.Text>
<Link to="/shipping">Edit</Link>
</Card.Body>
</Card>
<Card className="mb-3">
<Card.Body>
<Card.Title>Payment</Card.Title>
<Card.Text>
<strong>Method:</strong> {cart.paymentMethod}
</Card.Text>
<Link to="/payment">Edit</Link>
</Card.Body>
</Card>
<Card className="mb-3">
<Card.Body>
<Card.Title>Items</Card.Title>
<ListGroup variant="flush">
{cart.cartItems.map((item) => (
<ListGroup.Item key={item._id}>
<Row className="align-items-center">
<Col md={6}>
<img
src={item.image}
alt={item.name}
className="img-fluid rounded img-thumbnail"
></img>{" "}
<Link to={`/store/${item.slug}`}>{item.name}</Link>
</Col>
<Col md={3}>
<span>{item.quantity}</span>
</Col>
<Col md={3}>$ {item.price}</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
<Link to="/cart">Edit</Link>
</Card.Body>
</Card>
</Col>
<Col md={4}>
<Card>
<Card.Body>
<Card.Title>Order Summary</Card.Title>
<ListGroup variant="flush">
<ListGroup.Item>
<Row>
<Col>Items</Col>
<Col>$ {cart.itemsPrice.toFixed(2)}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Shipping</Col>
<Col>$ {cart.shippingPrice.toFixed(2)}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Tax</Col>
<Col>$ {cart.taxPrice.toFixed(2)}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>
<strong> Order Total</strong>
</Col>
<Col>
<strong>$ {cart.totalPrice.toFixed(2)}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<div className="d-grid">
<Button
type="button"
onClick={placeOrderHandler}
disabled={cart.cartItems.length === 0}
>
Place Order
</Button>
</div>
{loading && <LoadingBox></LoadingBox>}
</ListGroup.Item>
</ListGroup>
</Card.Body>
</Card>
</Col>
</Row>
</div>
);
}
Store.js file
export const Store = createContext();
//define initial state in cart based on local storage
const initialState = {
//check if user exists
userInfo: localStorage.getItem("userInfo")
? JSON.parse(localStorage.getItem("userInfo"))
: null,
cart: {
//get shipping address, payment method and cart items based on user
shippingAddress: localStorage.getItem("shippingAddress")
? JSON.parse(localStorage.getItem("shippingAddress"))
: {},
paymentMethod: localStorage.getItem("paymentMethod")
? localStorage.getItem("paymentMethod")
: "",
cartItems: localStorage.getItem("cartItems")
? JSON.parse(localStorage.getItem("cartItems"))
: [],
},
};
//update state in cart
//instead of creating duplicate items of the same product, we increase the amount of the one product if button is pressed more than once
function reducer(state, action) {
switch (action.type) {
//add items to cart
case "CART_ADD_ITEM":
const newItem = action.payload;
const existItem = state.cart.cartItems.find(
(item) => item._id === newItem._id
);
const cartItems = existItem
? state.cart.cartItems.map((item) =>
item._id === existItem._id ? newItem : item
)
: [...state.cart.cartItems, newItem];
localStorage.setItem("cartItems", JSON.stringify(cartItems));
return { ...state, cart: { ...state.cart, cartItems } };
//second case: remove item from cart
case "CART_REMOVE_ITEM": {
const cartItems = state.cart.cartItems.filter(
(item) => item._id !== action.payload._id
);
localStorage.setItem("cartItems", JSON.stringify(cartItems));
return { ...state, cart: { ...state.cart, cartItems } };
}
//clear cart
case "CART_CLEAR":
return { ...state, cart: { ...state.cart, cartItems: [] } };
//update user info based on data from the backend
case "USER_SIGNIN":
return { ...state, userInfo: action.payload };
//case where user is signed out
case "USER_SIGNOUT":
return {
...state,
userInfo: null,
cart: {
cartItems: [],
shippingAddress: {},
paymentMethod: "",
},
};
//update shipping address with data from payload
case "SAVE_SHIPPING_ADDRESS":
return {
...state,
cart: { ...state.cart, shippingAddress: action.payload },
};
//save payment method
case "SAVE_PAYMENT_METHOD":
return {
...state,
cart: { ...state.cart, paymentMethod: action.payload },
};
default:
return state;
}
}
export function StoreProvider(props) {
const [state, dispatch] = useReducer(reducer, initialState);
const value = { state, dispatch: dispatch };
return <Store.Provider value={value}>{props.children}</Store.Provider>;
}
Please provide any insights you have. I’m really not sure why the button gives this error. Thank you!!