首次提交workout表单触发PATCH /workouts/null 404报错排查
问题描述
表单填入数据点击提交按钮,首次提交时触发报错:PATCH http://localhost:5000/workouts/null 404 (Not Found)。首次报错后后续操作可正常运行,但所有首次提交操作都会触发该错误,初步判断为首次提交对象时向数据库传递了无效id。
附涉及的各部分代码:
Workout对象模型定义
import mongoose from "mongoose"; const workoutSchema=mongoose.Schema({ category:String, difficulty:String, exercisesNames:[String], selectedFile:String, likeCount: { type:Number, default:0 }, postedAt:{ type:Date, default:new Date() } }); const WorkoutMessage=mongoose.model('WorkoutMessage',workoutSchema); export default WorkoutMessage;
后端接口路由代码
import express from 'express'; import mongoose from 'mongoose'; import WorkoutMessage from '../models/workoutMessage.js' const router = express.Router(); export const getWorkouts=async (req,res)=>{ try{ const workoutMessages=await WorkoutMessage.find(); console.log(workoutMessages); res.status(200).json(workoutMessages); }catch(error){ res.status(404).json({message:error.message}); } } export const createWorkout=async (req,res)=>{ const workout=req.body; const newWorkoutMessage=new WorkoutMessage(workout); try{ await newWorkoutMessage.save(); res.status(201).json(newWorkoutMessage); }catch(error) { res.status(409).json({message:error.message}); } } export const updateWorkout=async(req,res)=>{ const { id:_id }=req.params; const workout=req.body; if(!mongoose.Types.ObjectId.isValid(_id)) return res.status(404).send(`No workout with that ${_id}`); const updatedWorkout=await WorkoutMessage.findByIdAndUpdate(_id,{...workout,_id},{new:true}); res.json(updateWorkout); } export const deleteWorkout=async(req,res)=>{ const { id }=req.params; if(!mongoose.Types.ObjectId.isValid(id)) return res.status(404).send('No workout with that id'); await WorkoutMessage.findByIdAndRemove(id); res.json({message:"Workout deleted successfully"}); } export default router;
客户端action函数代码
import { FETCH_ALL, CREATE, UPDATE, DELETE } from '../constants/actionTypes'; import * as api from '../api'; export const getWorkouts=()=> async (dispatch)=>{ try{ const {data}=await api.fetchWorkouts(); dispatch({type:FETCH_ALL,payload:data}); }catch(error){ console.log(error); } } export const createWorkout=(workout)=>async(dispatch)=>{ try{ const {data}=await api.createWorkout(workout); dispatch({type:CREATE,payload:data}); console.log(data); }catch(error){ console.log(error.message); } } export const updateWorkout=(id,workout)=>async(dispatch)=>{ try{ const {data}=await api.updateWorkout(id,workout); dispatch({type:UPDATE,payload:data}) }catch(error){ console.log(error); } } export const deleteWorkout=(id)=>async (dispatch)=>{ try{ await api.deleteWorkout(id); dispatch({type:DELETE,payload:id}); }catch(error){ console.log(error); } }
表单Form组件代码
const Form=({ currentId, setCurrentId=()=>{} })=>{ const [workoutData, setWorkoutData]=useState({ category:'',difficulty:'', exercisesNames:'',selectedFile:'' }); const workout = useSelector((state) => (currentId ? state.workouts.find((workout) => workout._id === currentId) : null)); const dispatch=useDispatch(); const classes=useStyles(); useEffect(()=>{ if(workout) setWorkoutData(workout); },[workout]); const clear = () => { setCurrentId(0); setWorkoutData({ category:'',difficulty:'', exercisesNames:'',selectedFile:'' }); }; const handleSubmit=async (e)=>{ e.preventDefault(); if(currentId===0){ dispatch(createWorkout(workoutData)); clear(); } else{ dispatch(updateWorkout(currentId, workoutData)); clear(); } }; return( <Paper className={classes.paper}> <form autoComplete="off" noValidate className={`${classes.root} ${classes.form}`} onSubmit={handleSubmit}> <Typography variant="h6"></Typography> <TextField name="category" variant="outlined" label="Category" fullWidth value={workoutData.category} onChange={(e)=>setWorkoutData({...workoutData, category:e.target.value})}/> <TextField name="difficulty" variant="outlined" label="Difficulty" fullWidth value={workoutData.difficulty} onChange={(e)=>setWorkoutData({...workoutData, difficulty:e.target.value})}/> <TextField name="exercisesNames" variant="outlined" label="Exercises names" fullWidth value={workoutData.exercisesNames} onChange={(e)=>setWorkoutData({...workoutData, exercisesNames:e.target.value})}/> <div className={classes.fileInput}> <FileBase type="file" multiple={false} onDone={({base64})=>setWorkoutData({...workoutData,selectedFile:base64})} /> </div> <Button className={classes.buttonSubmit} variant="contained" color="primary" size="large" type="submit" fullWidth>Submit</Button> <Button variant="contained" color="secondary" size="small" onClick={clear} fullWidth>Clear</Button> </form> </Paper> ); } export default Form;
根因定位
- 核心触发原因:Form组件仅给
setCurrentId设置了默认空函数,未给currentId属性设置默认值。首次加载时如果父组件未传入currentId,该值为undefined/null,不满足currentId===0的新建逻辑判断条件,错误走到更新分支,携带空id发起PATCH请求,最终请求路径变为/workouts/null触发404。首次提交后clear方法将currentId设为0,后续提交判断逻辑正常,因此不会再触发该错误。 - 附带隐藏bug:后端
updateWorkout接口返回响应时,变量名拼写错误,将存储更新结果的updatedWorkout错写为函数名updateWorkout,会导致更新操作执行成功但接口返回500错误。
修复步骤
1. 修正Form组件的props默认值与提交判断逻辑
给currentId设置默认值0,同时将严格相等判断改为falsy判断,兼容空值场景:
// 原代码 // const Form=({ currentId, setCurrentId=()=>{} })=>{ // 修改为 const Form=({ currentId = 0, setCurrentId=()=>{} })=>{ // 省略其他代码 const handleSubmit=async (e)=>{ e.preventDefault(); // 原判断 if(currentId===0) 改为 if(!currentId){ dispatch(createWorkout(workoutData)); clear(); } else{ dispatch(updateWorkout(currentId, workoutData)); clear(); } };
同时确认父组件中控制currentId的state初始值设为0,参考写法:
const [currentId, setCurrentId] = useState(0); // 渲染Form时传入 <Form currentId={currentId} setCurrentId={setCurrentId} />
2. 修复后端updateWorkout接口的变量拼写错误
export const updateWorkout=async(req,res)=>{ const { id:_id }=req.params; const workout=req.body; if(!mongoose.Types.ObjectId.isValid(_id)) return res.status(404).send(`No workout with that ${_id}`); const updatedWorkout=await WorkoutMessage.findByIdAndUpdate(_id,{...workout,_id},{new:true}); // 原代码错写为res.json(updateWorkout),修改为 res.json(updatedWorkout); }
内容的提问来源于stack exchange,提问作者Alexandra DDT
相关产品推荐
相关产品推荐

