React全栈项目编辑咖啡出现CastError并自动登出求助
问题描述
数据库连接正常,可完成认证登录并渲染存储的咖啡列表,但点击编辑某条咖啡时出现如下错误:
const castError = new CastError(); [1] ^ [1] [1] CastError: Cast to ObjectId failed for value ":id" (type string) at path "_id" for model "Coffee"
触发错误后会自动登出,数据未保存且数据库连接中断。
后端代码
coffeeRoutes.ts
const router = Router(); router.get('/', getAllCoffee); router.get('/:id', getOneCoffee); router.post('/', addCoffee); router.put('/:id', editCoffee); router.delete('/:id', deleteCoffee); export default router;
coffee.ts
import { Document, Schema, Model, model } from 'mongoose'; interface ICoffee extends Document { name: string; description: string; price: number; } const coffeeSchema: Schema = new Schema({ name: { type: String, required: true, unique: true }, description: { type: String, required: true }, price: { type: Number, required: true } }); const Coffee: Model<ICoffee> = model<ICoffee>('Coffee', coffeeSchema); export { ICoffee, Coffee };
coffeeController.ts
export const getAllCoffee: RequestHandler = async (req, res, next) => { let coffeeList = await Coffee.find(); res.status(200).json(coffeeList); } export const getOneCoffee: RequestHandler = async (req, res, next) => { let itemId = req.params.id; let coffee = await Coffee.findById(itemId); res.status(200).json(coffee); } export const addCoffee: RequestHandler = async (req, res, next) => { let user: IUser | null = await verifyUser(req); if (!user) { return res.status(403).send(); } const newCoffee: ICoffee = new Coffee({ name: req.body.name, description: req.body.description, price: req.body.price }); try { await newCoffee.save(); res.status(201).json(newCoffee); } catch (err) { res.status(500).send(err); } } export const editCoffee: RequestHandler = async (req, res, next) => { let user: IUser | null = await verifyUser(req); if (!user) { return res.status(403).send(); } let itemId = req.params.id; const updatedCoffee: ICoffee = new Coffee({ _id: itemId, name: req.body.name, description: req.body.description, price: req.body.price }); await Coffee.findByIdAndUpdate(itemId, { $set: updatedCoffee }) res.status(200).json(updatedCoffee); } export const deleteCoffee: RequestHandler = async (req, res, next) => { let user: IUser | null = await verifyUser(req); if (!user) { return res.status(403).send(); } let itemId = req.params.id; let result = await Coffee.findByIdAndDelete(itemId); res.status(200).json(result); }
App.ts
const connectionString: string = 'mongodb://localhost:27017/testDB' mongoose.connect(connectionString).then( () => console.log('database connection successful!'), err => console.log('Error connecting to the database', err)); const app = express(); app.use(morgan('dev')); app.use(express.json()); app.use(express.urlencoded({extended: true})); const cors = require('cors'); const corsOptions = { origin: [ 'http://localhost:3001' ] }; app.use(cors(corsOptions)); // routes app.use('/api/coffee', coffeeRoutes); app.use('/api/users', userRoutes); app.use((req: Request, res: Response, next: NextFunction) => { res.status(404).end(); }); app.listen(3000);
终端报错信息
[1] OPTIONS /api/coffee/:id 204 0.696 ms - 0 [1] { [1] host: 'localhost:3000', [1] connection: 'keep-alive', [1] 'content-length': '424', [1] accept: 'application/json, text/plain, */*', [1] authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2MzE2YzBiNjQyMjYyNDM1YzdiYWZhOTgiLCJpYXQiOjE2NjI1MjE5NjksImV4cCI6MTY2MjUyNTU2OX0.fAusx0ov8IjLA10YXZqL-OljrtShkUjMIA7SveC357k', [1] 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36', [1] 'content-type': 'application/x-www-form-urlencoded', [1] 'sec-gpc': '1', [1] origin: 'http://localhost:3001', [1] 'sec-fetch-site': 'same-site', [1] 'sec-fetch-mode': 'cors', [1] 'sec-fetch-dest': 'empty', [1] referer: 'http://localhost:3001/', [1] 'accept-encoding': 'gzip, deflate, br', [1] 'accept-language': 'en-US,en;q=0.9' [1] } [1] /home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/query.js:4884 [1] const castError = new CastError(); [1] ^ [1] [1] CastError: Cast to ObjectId failed for value ":id" (type string) at path "_id" for model "Coffee" [1] at model.Query.exec (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/query.js:4884:21) [1] at Query.then (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/query.js:4983:15) [1] at process.processTicksAndRejections (node:internal/process/task_queues:95:5) { [1] messageFormat: undefined, [1] stringValue: '":id"', [1] kind: 'ObjectId', [1] value: ':id', [1] path: '_id', [1] reason: BSONTypeError: Argument passed in must be a string of 12 bytes or a string of 24 hex characters or an integer [1] at new BSONTypeError (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/bson/lib/error.js:41:28) [1] at new ObjectId (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/bson/lib/objectid.js:67:23) [1] at castObjectId (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/cast/objectid.js:25:12) [1] at ObjectId.cast (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/schema/objectid.js:246:12) [1] at SchemaType.applySetters (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/schematype.js:1201:12) [1] at SchemaType._castForQuery (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/schematype.js:1648:15) [1] at SchemaType.castForQuery (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/schematype.js:1636:15) [1] at SchemaType.castForQueryWrapper (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/schematype.js:1612:20) [1] at cast (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/cast.js:347:32) [1] at Query.cast (/home/fgg/Desktop/backend-ts/l13-frontend/coffee-api/node_modules/mongoose/lib/query.js:5312:12), [1] valueType: 'string' [1] }
前端代码
EditCoffee组件
import React, { useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import axios from 'axios'; export const EditCoffee = () => { const [coffee, setCoffee] = useState({ name: "", description: "", price: "" }); let params = useParams(); // let { editCoffee } = useContext(CoffeeProvider); function editCoffee(coffee, id) { let token = localStorage.getItem('myCoffeeToken') let headers = { Authorization: 'Bearer ' + token }; return axios.put(baseUrl + id, coffee, { headers }) .then(response => { getAllCoffee(); return new Promise(resolve => resolve(response.data)); } ); } function getAllCoffee() { return axios.get(baseUrl).then(response => setCoffee(response.data)) }; const baseUrl = "http://localhost:3000/api/coffee/"; let navigate = useNavigate(); function handleChange(event) { setCoffee((prevValue) => { return { ...prevValue, [event.target.name]: event.target.value } }); } function handleSubmit(event) { event.preventDefault(); editCoffee(editCoffee, params.id).then(() => { navigate(`/coffee`); }).catch(error => { console.log(error); navigate(`/signin`); }); } return ( <form onSubmit={handleSubmit}> <h1>EDIT COFFEE</h1> <span>Coffee Name </span> <input placeholder="Enter coffee name" type="text" name="name" value={coffee.name} onChange={handleChange} /> <br></br><br></br> <span>Description </span> <input placeholder="Enter description" type="text" name="description" value={coffee.description} onChange={handleChange} /> <br></br><br></br> <span>Price </span> <input placeholder="Enter price" type="number" name="price" value={coffee.price} onChange={handleChange} /> <br></br><br></br> <button type='submit'>Edit Coffee</button> </form> ) };
CoffeeList组件
import React from 'react'; import CoffeeContext from '../contexts/CoffeeContext'; import { Link } from "react-router-dom"; function CoffeeList(props) { return ( <CoffeeContext.Consumer> { ({ coffee }) => { return <div> <h1>Coffee List</h1> <Link to="/coffee/new">Add New Coffee</Link> <div> {coffee.map((c) => { return ( <div key={c.id}> <h2>{c.name} | ${c.price}</h2> <p>{c.description}</p> <Link to={`/edit/${c.id}`}> <button>Edit</button> </Link> {/* <a href="/edit/id"><button>Edit</button></a> */} <button>Delete</button> </div> ) })} </div> </div> } } </CoffeeContext.Consumer> ); } export default CoffeeList;
问题修复方案
1. 核心修复:前端请求参数错误
从终端日志可见,请求URL为/api/coffee/:id,说明前端把路由占位符:id当成了实际ID值,未替换为咖啡的真实ID:
- EditCoffee组件handleSubmit函数:调用
editCoffee时传参错误,将函数本身传入,应改为传入状态中的coffee对象:// 错误代码 editCoffee(editCoffee, params.id) // 修复后 editCoffee(coffee, params.id) - 确认React Router路由配置:确保Edit页面的路由定义为
path="/edit/:id",保证useParams()能正确获取ID。
2. 修复CoffeeList组件的ID字段
Mongoose文档的ID字段是_id而非id,遍历列表时需修正:
// 错误代码 <div key={c.id}> <Link to={`/edit/${c.id}`}> // 修复后 <div key={c._id}> <Link to={`/edit/${c._id}`}>
3. 后端editCoffee控制器优化
- 无需创建新Coffee实例,直接传入更新数据即可,同时添加错误捕获避免连接中断:
export const editCoffee: RequestHandler = async (req, res, next) => { let user: IUser | null = await verifyUser(req); if (!user) { return res.status(403).send(); } try { const updateData = { name: req.body.name, description: req.body.description, price: req.body.price }; // { new: true } 返回更新后的文档 const updatedCoffee = await Coffee.findByIdAndUpdate(req.params.id, updateData, { new: true }); res.status(200).json(updatedCoffee); } catch (err) { res.status(400).json({ error: err.message }); } }
4. 自动登出问题修复
自动登出是因为请求失败触发了catch块的navigate('/signin'),修复上述请求错误后该问题会自动解决。
内容的提问来源于stack exchange,提问作者Aquiles Bailo
相关产品推荐
相关产品推荐

