You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MERN栈教程中Create按钮无响应及Failed to fetch问题求助

问题描述

按照MongoDB官方MERN栈简易记录列表教程完成所有步骤,已确认数据库连接正常,但测试时出现以下问题:

  • 点击Create Person按钮创建新记录无反应;
  • 刷新创建页面弹出Failed to fetch错误提示;
  • 刷新后再次点击Create Person按钮,不再弹出提示框,且按钮无法跳转到首页;
  • 记录列表无法显示数据。

前端 create.js 代码

// /client/src/components/create.js

import React, { useState } from "react";
import { useNavigate } from "react-router";
export default function Create() {
 const [form, setForm] = useState({
   name: "",
   position: "",
   level: "",
 });
 const navigate = useNavigate();

 // 更新表单状态
 function updateForm(value) {
   return setForm((prev) => {
     return { ...prev, ...value };
   });
 }

 // 表单提交处理
 async function onSubmit(e) {
   e.preventDefault();

   const newPerson = { ...form };

   await fetch("http://localhost:5000/record/add", {
     method: "POST",
     headers: {
       "Content-Type": "application/json",
     },
     body: JSON.stringify(newPerson),
   })
   .catch(error => {
     console.log(error);
     return;
   });

   setForm({ name: "", position: "", level: "" });
   navigate("/");
 }

 // 渲染表单
 return (
   <div>
     <h3>Create New Record</h3>
     <form onSubmit={onSubmit}>
       <div className="form-group">
         <label htmlFor="name">Name</label>
         <input
           type="text"
           className="form-control"
           id="name"
           value={form.name}
           onChange={(e) => updateForm({ name: e.target.value })}
         />
       </div>
       <div className="form-group">
         <label htmlFor="position">Position</label>
         <input
           type="text"
           className="form-control"
           id="position"
           value={form.position}
           onChange={(e) => updateForm({ position: e.target.value })}
         />
       </div>
       <div className="form-group">
         <div className="form-check form-check-inline">
           <input
             className="form-check-input"
             type="radio"
             name="positionOptions"
             id="positionIntern"
             value="Intern"
             checked={form.level === "Intern"}
             onChange={(e) => updateForm({ level: e.target.value })}
           />
           <label htmlFor="positionIntern" className="form-check-label">Intern</label>
         </div>
         <div className="form-check form-check-inline">
           <input
             className="form-check-input"
             type="radio"
             name="positionOptions"
             id="positionJunior"
             value="Junior"
             checked={form.level === "Junior"}
             onChange={(e) => updateForm({ level: e.target.value })}
           />
           <label htmlFor="positionJunior" className="form-check-label">Junior</label>
         </div>
         <div className="form-check form-check-inline">
           <input
             className="form-check-input"
             type="radio"
             name="positionOptions"
             id="positionSenior"
             value="Senior"
             checked={form.level === "Senior"}
             onChange={(e) => updateForm({ level: e.target.value })}
           />
           <label htmlFor="positionSenior" className="form-check-label">Senior</label>
         </div>
       </div>
       <div className="form-group">
         <input
           type="submit"
           value="Create person"
           className="btn btn-primary"
         />
       </div>
     </form>
   </div>
 );
}

前端 recordList.js 代码

// /client/src/components/recordList.js

import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
const Record = (props) => (
 <tr>
   <td>{props.record.name}</td>
   <td>{props.record.position}</td>
   <td>{props.record.level}</td>
   <td>
     <Link className="btn btn-link" to={`/edit/${props.record._id}`}>Edit</Link> |
     <button className="btn btn-link"
       onClick={() => {
         props.deleteRecord(props.record._id);
       }}
     >
       Delete
     </button>
   </td>
 </tr>
);
export default function RecordList() {
 const [records, setRecords] = useState([]);

 // 获取记录数据
 useEffect(() => {
   async function getRecords() {
     const response = await fetch(`http://localhost:5000/record/`);

     if (!response.ok) {
       const message = `An error occurred: ${response.statusText}`;
       window.alert(message);
       return;
     }

     const records = await response.json();
     setRecords(records);
   }

   getRecords();

   return;
 }, [records.length]);

 // 删除记录
 async function deleteRecord(id) {
   await fetch(`http://localhost:5000/${id}`, {
     method: "DELETE"
   });

   const newRecords = records.filter((el) => el._id !== id);
   setRecords(newRecords);
 }

 // 渲染记录列表
 function recordList() {
   return records.map((record) => {
     return (
       <Record
         record={record}
         deleteRecord={() => deleteRecord(record._id)}
         key={record._id}
       />
     );
   });
 }

 // 渲染页面
 return (
   <div>
     <h3>Record List</h3>
     <table className="table table-striped" style={{ marginTop: 20 }}>
       <thead>
         <tr>
           <th>Name</th>
           <th>Position</th>
           <th>Level</th>
           <th>Action</th>
         </tr>
       </thead>
       <tbody>{recordList()}</tbody>
     </table>
   </div>
 );
}

后端 record.js 代码

const express = require("express");

// 定义记录路由
const recordRoutes = express.Router();

// 数据库连接工具
const dbo = require("../db/conn");

// ObjectId转换工具
const ObjectId = require("mongodb").ObjectId;


// 获取所有记录
recordRoutes.route("/record").get(function (req, res) {
 let db_connect = dbo.getDb("todolist");
 db_connect
   .collection("records")
   .find({})
   .toArray(function (err, result) {
     if (err) throw err;
     res.json(result);
   });
});

// 根据ID获取单条记录
recordRoutes.route("/record/:id").get(function (req, res) {
 let db_connect = dbo.getDb("todolist");
 let myquery = { _id: ObjectId(req.params.id) };
 db_connect
   .collection("records")
   .findOne(myquery, function (err, result) {
     if (err) throw err;
     res.json(result);
   });
});

// 创建新记录
recordRoutes.route("/record/add").post(function (req, response) {
 let db_connect = dbo.getDb("todolist");
 let myobj = {
   name: req.body.name,
   position: req.body.position,
   level: req.body.level,
 };
 db_connect.collection("records").insertOne(myobj, function (err, res) {
   if (err) throw err;
   response.json(res);
 });
});

// 更新记录
recordRoutes.route("/update/:id").post(function (req, response) {
 let db_connect = dbo.getDb("todolist");
 let myquery = { _id: ObjectId(req.params.id) };
 let newvalues = {
   $set: {
     name: req.body.name,
     position: req.body.position,
     level: req.body.level,
   },
 };
 db_connect
   .collection("records")
   .updateOne(myquery, newvalues, function (err, res) {
     if (err) throw err;
     console.log("1 document updated");
     response.json(res);
   });
});

// 删除记录
recordRoutes.route("/:id").delete((req, response) => {
 let db_connect = dbo.getDb("todolist");
 let myquery = { _id: ObjectId(req.params.id) };
 db_connect.collection("records").deleteOne(myquery, function (err, obj) {
   if (err) throw err;
   console.log("1 document deleted");
   response.json(obj);
 });
});

module.exports = recordRoutes;

问题排查与修复方案

1. 跨域资源共享(CORS)问题

前端默认运行在3000端口,后端在5000端口,浏览器会拦截跨域请求,导致Failed to fetch错误。
修复:

  • 后端安装cors包:npm install cors
  • 在后端主入口文件(如server.js)中添加:
const cors = require('cors');
app.use(cors());

2. Delete请求路径错误

recordList.js中delete请求路径为http://localhost:5000/${id},但后端delete路由/:id挂载在/record路由下,实际有效路径应为/record/${id}。
修复:
修改recordList.js中的delete请求:

await fetch(`http://localhost:5000/record/${id}`, {
  method: "DELETE"
});

3. useEffect依赖循环问题

recordList.js中useEffect依赖records.length,每次删除记录后长度变化会触发重复请求,导致不必要的资源消耗。
修复:
将useEffect依赖改为空数组,仅在组件挂载时请求一次数据:

useEffect(() => {
  // 原有getRecords代码
}, []);

4. 错误处理逻辑缺陷

create.js中fetch的catch仅打印错误,未阻止后续的navigate操作,导致请求失败时仍会跳转到首页。
修复:
修改onSubmit函数,仅在请求成功后重置表单并跳转:

async function onSubmit(e) {
  e.preventDefault();
  const newPerson = { ...form };

  try {
    const response = await fetch("http://localhost:5000/record/add", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(newPerson),
    });
    if (!response.ok) {
      throw new Error('创建请求失败');
    }
    setForm({ name: "", position: "", level: "" });
    navigate("/");
  } catch (error) {
    console.error(error);
    alert('创建记录失败,请检查输入或连接状态');
  }
}

内容的提问来源于stack exchange,提问作者Hoko L

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 11:55:54