I am trying to introduce 'cartItems' functionality to my react-redux app and store the added data in the browser's local storage.
Indeed the problem raises when I try to show cart items by clicking on the cart link at the navbar section. The error message is 'GET http://localhost:3000/products/undefined 500 (Internal Server Error)' and 'Uncaught (in promise)'. and I don't know how to fix the issue.
Note: the same component 'CartScreen.js' would display the cart items in both cases, when adding new items to the cart & when also clicking on the cart link at the navbar.
Please follow the code snippets
Thanks & Regards
App.js
import Header from './components/Header';
import { Container } from 'react-bootstrap';
import HomeScreen from './screens/HomeScreen';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import ProductScreen from './screens/ProductScreen';
import CartScreen from './screens/CartScreen';
function App() {
return (
<Router>
<Header />
<main className="py-3">
<Container>
<Routes>
<Route path="/" element={<HomeScreen />} exact />
<Route path="/product/">
<Route path=":id" element={<ProductScreen />} />
<Route index element={<ProductScreen />} />
</Route>
<Route path="/cart" >
<Route index element={<CartScreen />} />
<Route path=":productid" element={<CartScreen />} />
</Route>
</Routes>
</Container>
</main>
<Footer />
</Router>
);
}
export default App;
ProductScreen.js
import { useParams, Link, useNavigate } from 'react-router-dom';
import {Row,Col,Image,ListGroup,Button,Card,Form} from 'react-bootstrap';
import Rating from '../components/Rating';
import { listProductDetails } from '../actions/productActions';
import { useDispatch, useSelector } from 'react-redux';
import Loader from '../components/Loader';
import Message from '../components/Message';
function ProductScreen() {
const { id } = useParams();
const navigate = useNavigate();
const [qty, setQty] = useState(1);
const dispatch = useDispatch();
const productListDetail = useSelector((state) => state.productDetail);
const { loading, error, product } = productListDetail;
useEffect(() => {
dispatch(listProductDetails(id));
}, [dispatch, id]);
const addToCartHandler = () => {
navigate(`/cart/${id}?qty=${qty}`);
};
return (
<div> <Link to={-1} className="btn btn-primary my-3">Go Back</Link>
{loading ? (<Loader />): error ? (<Message variant="danger">{error}</Message>) : (
<Row>
<Col md={6}>
<Image src={product.image} alt={product.name} fluid />
</Col>
<Col md={3}>
<ListGroup variant="flush">
<ListGroup.Item>
<h3> {product.name}</h3>
</ListGroup.Item>
<ListGroup.Item>
<Rating value={product.rating} text={`${product.numReviews} reviews`}
color={'#fae500'}/>
</ListGroup.Item>
<ListGroup.Item>Price: ${product.price}</ListGroup.Item>
<ListGroup.Item>
Description: {product.description}
</ListGroup.Item>
</ListGroup>
</Col>
<Col md={3}>
<Card>
<ListGroup variant="flush">
<ListGroup.Item>
<Row>
<Col> Price: </Col>
<Col>
<strong>${product.price} </strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col> Status: </Col>
<Col>
<strong>
{product.countInStock > 0 ? 'In Stock' : 'Out of Stock'}
</strong>
</Col>
</Row>
</ListGroup.Item>
{product.countInStock > 0 && (
<ListGroup.Item>
<Row>
<Col> Qty </Col>
<Col xs="auto" className="my-1">
<Form.Control as="select" value={qty}
onChange={(e) => setQty(e.target.value)}>
{[...Array(product.countInStock).keys()].map((x) => (
<option key={x + 1} value={x + 1}>{x + 1}</option>))}
</Form.Control>
</Col>
</Row>
</ListGroup.Item>)}
<ListGroup.Item>
<Button onClick={addToCartHandler}
className="btn btn-primary container-fluid"
disabled={product.countInStock === 0}
type="button">
Add to Cart
</Button>
</ListGroup.Item>
</ListGroup>
</Card>
</Col>
</Row>
)}
</div>
);
}
export default ProductScreen;
CartScreen.js
import React, { useEffect } from 'react';
import { Col, ListGroup,Row,} from 'react-bootstrap';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation, useNavigate, useParams, Link, Outlet } from 'react-router-dom';
import { addToCart } from '../actions/cartAction';
import Message from '../components/Message';
const CartScreen = () => {
const { search } = useLocation();
const { productid } = useParams();
const qty = search ? Number(search.split('=')[1]) : 1;
const dispatch = useDispatch();
const cart = useSelector((state) => state.cart);
const { cartItems } = cart;
useEffect(() => {
dispatch(addToCart(productid, qty));
},[dispatch, productid, qty]);
return (
<Row>
<Col md={8}> {cartItems.length === 0 ? (<Message variant="info">
Go Back To Home Page <Link to="/"></Link> </Message> ) : (
<ListGroup> {cartItems.map((x) => (
<ListGroup.Item key={x.product}>
{x.name} , {x.qty}
</ListGroup.Item> ))}
</ListGroup>)}
</Col>
<Col md={4}></Col>
</Row>
);
};
export default CartScreen;
cartReducers.js
import { CART_ADD_ITEM } from '../constants/cartConstants';
export const cartReducer = (state = { cartItems: [] }, action) => {
switch (action.type) {
case CART_ADD_ITEM:
const item = action.payload;
const existItem = state.cartItems.find((x) => x.product === item.product);
if (existItem) {
return {
...state, cartItems: state.cartItems.map((x) =>
x.product === existItem.product ? item : x),};}
else {
return {
...state, cartItems: [...state.cartItems, item],};}
default:
return state;
}
};
cartAction.js
import axios from 'axios';
import { CART_ADD_ITEM } from '../constants/cartConstants';
export const addToCart = (productid, qty) => async (dispatch, getState) => {
const { data } = await axios.get(`/products/${productid}`);
dispatch({
type: CART_ADD_ITEM,
payload: {
product: data._id,
name: data.name,
image: data.image,
price: data.price,
countInStock: data.countInStock,
qty,
},
});
localStorage.setItem('cartItems', JSON.stringify(getState().cart.cartItems));
};
store.js
import { legacy_createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from '@redux-devtools/extension';
import {
productDetailsReducer,
productListReducer,
} from './reducers/productReducers';
import { cartReducer } from './reducers/cartReducers';
const reducer = combineReducers({
productList: productListReducer,
productDetail: productDetailsReducer,
cart: cartReducer,
});
const cartItemsFromStorage = localStorage.getItem('cartItems')
? JSON.parse(localStorage.getItem('cartItems'))
: [];
const initialState = { cart: { cartItems: cartItemsFromStorage } };
const middleware = [thunk];
const store = legacy_createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;