MERN项目商品列表跳转详情页出现404错误问题排查
问题描述
MERN项目中,从商品列表点击进入单个商品详情页时触发404错误,跳转后的URL为 http://localhost:3000/product/undefined;但直接在URL中输入合法商品ID(如http://localhost:3000/product/6376ae043f18b79b043c5b89),却能正常加载详情页。
相关代码文件
productScreen.js
import React, { useState, useEffect } from "react"; import { Link, useNavigate, useParams } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; import { Row, Col, Image, ListGroup, Card, Button, Form, } from "react-bootstrap"; import Rating from "../components/Rating"; import Message from "../components/Message"; import Loader from "../components/Loader"; import { listProductDetails } from "../actions/productActions.js"; const ProductScreen = () => { const { id } = useParams(); const productId = `${encodeURIComponent(id)}`; const dispatch = useDispatch(); const history = useNavigate(); const [qty, setQty] = useState(1); const productDetails = useSelector((state) => state.productDetails); const { loading, error, product } = productDetails; useEffect(() => { dispatch(listProductDetails(productId)); }, [dispatch, productId]); const addToCartHandler = () => { history(`/cart/${productId}?qty=${qty}`); }; return ( <> <Link className="btn btn-light my-3" to="/"> 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`} /> </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> {product.countInStock > 0 ? "In Stock" : "Out Of Stock"} </Col> </Row> </ListGroup.Item> {product.countInStock > 0 && ( <ListGroup.Item> <Row> <Col>Qty</Col> <Col> <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-block" type="button" disabled={product.countInStock === 0} > Add To Cart </Button> </ListGroup.Item> </ListGroup> </Card> </Col> </Row> )} </> ); }; export default ProductScreen;
productAction.js
import axios from 'axios'; import { PRODUCT_LIST_REQUEST, PRODUCT_LIST_SUCCESS, PRODUCT_LIST_FAIL, PRODUCT_DETAILS_REQUEST, PRODUCT_DETAILS_SUCCESS, PRODUCT_DETAILS_FAIL, } from '../constants/productConstants.js'; export const listProducts = () => async (dispatch) => { try { dispatch({ type: PRODUCT_LIST_REQUEST }); const { data } = await axios.get('/api/products'); dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data, }); } catch (error) { dispatch({ type: PRODUCT_LIST_FAIL, payload: error.response && error.response.data.message ? error.response.data.message : error.message, }); } }; export const listProductDetails = (id) => async (dispatch) => { try { dispatch({ type: PRODUCT_DETAILS_REQUEST }); const { data } = await axios.get( `/api/products/${id}` ); 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, }); } };
productRoutes.js
import express from 'express'; import Product from '../models/productModel.js'; import asyncHandler from 'express-async-handler'; import {ObjectId} from 'mongoDB'; import mongoose from 'mongoose'; const router = express.Router(); // @desc fetch all products // @droute GET /api/products // @access public router.get( '/', asyncHandler(async (req, res) => { const products = await Product.find({}); return res.json(products); }) ); // @desc fetch oneproducts // @droute GET /api/products/:id // @access public router.get( '/:id([0-9a-fA-F]{24})', asyncHandler(async (req, res) => { const id = mongoose.Types.ObjectId(req.params.id.trim()); const product = await Product.findById(id); if (product) { return res.json(product); } else { return res.status(404).json({ message: 'Product not found' }); //throw new Error('Product not found'); } //return res.json(product); }) ); export default router;
App.js
import React from 'react'; import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; import { Container } from 'react-bootstrap'; import Header from './components/Header'; import Footer from './components/Footer'; import HomeScreen from './screens/HomeScreen'; import ProductScreen from './screens/ProductScreen'; import { CartScreen } from './screens/CartScreen'; const App = () => { return ( <Router> <Header /> <main className='py-3'> <Container> <Routes> <Route path='/product/:id' element={<ProductScreen />} /> <Route path='/cart' element={<CartScreen />} /> <Route path='/' element={<HomeScreen />} /> </Routes> </Container> </main> <Footer /> </Router> ); }; export default App;

问题根源与解决方案
核心问题
跳转时传递的商品ID为undefined,导致URL路径错误。问题出在商品列表页(HomeScreen)的跳转链接上,你可能错误引用了不存在的字段(比如用product.id而非MongoDB默认的product._id)。
修复步骤
修正HomeScreen中的跳转链接
找到商品列表项的跳转代码,将ID字段改为_id:
错误写法(示例):<Link to={`/product/${product.id}`}>查看详情</Link>正确写法:
<Link to={`/product/${product._id}`}>查看详情</Link>若后端返回的商品ID字段不是
_id,则替换为实际字段名。验证参数传递有效性
在ProductScreen中添加日志验证ID是否正确接收:const { id } = useParams(); console.log('接收的商品ID:', id);若打印结果为
undefined,说明跳转时确实未传递有效ID。后端路由优化(可选)
无需手动转换ObjectId,findById可直接处理合法的ID字符串,简化路由代码:router.get('/:id', asyncHandler(async (req, res) => { const product = await Product.findById(req.params.id); if (product) { return res.json(product); } else { return res.status(404).json({ message: 'Product not found' }); } }));添加跳转防护(可选)
在列表页对ID做非空判断,避免无效跳转:{product._id && <Link to={`/product/${product._id}`}>查看详情</Link>}
内容的提问来源于stack exchange,提问作者Flora332
相关产品推荐
相关产品推荐

