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

React.js应用PUT请求单字段修改后其余字段为null问题排查

问题根因
  • 前端更新后状态覆盖错误:updateHeaderData请求成功后,后端仅返回操作状态对象(仅含status、message字段),直接执行setHeaderData(data)会把存储了完整表单值的原有状态覆盖。后续编辑单个字段时,onChange仅合并当前修改的字段,其余未修改字段值为undefined,传入后端后会被转成null存入数据库。
  • 后端PUT接口SQL逻辑错误:UPDATE header SET name = ?, title = ?, linkedIn = ?, github = ?语句未添加WHERE定位条件,会将header表内所有行的数据全部更新为传入值,属于高危语法错误。
  • 前端状态初始化与提交判断失效:headerData初始值为空对象{},JS中空对象为真值,导致if (!headerData)判断完全失效——未拉取到已有数据时提交也会走更新逻辑;同时初始空对象会让表单输入框首次渲染时value为undefined,触发React受控/非受控组件切换警告,存在值丢失风险。
  • 后端错误响应写法错误:res.sendStatus("400").json(...)写法会触发运行时报错,sendStatus方法已经完成响应发送,无法再链式调用json返回数据。
修复代码

1. ApiContext.js 修正

import { createContext, useState } from "react";

const ApiContext = createContext();
export default ApiContext;

export const ApiProvider = ({ children }) => {
    // 初始值改为null,避免空对象真值判断问题
    const [headerData, setHeaderData] = useState(null);

    const onChange = (e) => {
        setHeaderData((prev) => ({
            ...prev,
            [e.target.name]: e.target.value.trim(),
        }));
    };

    const retrieveHeaderData = async () => {
        try {
            let response = await fetch("/api/retrieve/header-data", {
                method: "GET",
                headers: {
                    "Content-Type": "application/json",
                },
            });
            let data = await response.json();
            if (response.status === 200) {
                // 假设接口返回的数组第一条就是要编辑的配置
                setHeaderData(data[0] || null);
            }
        } catch (err) {
            console.error("拉取头信息失败:", err);
        }
    }

    const submitHeaderData = async (formData) => {
        try {
            let response = await fetch("/api/submit/header-data", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                },
                body: JSON.stringify(formData),
            });
            let data = await response.json();
            if (response.ok) {
                // 提交成功后重新拉取最新数据,保证状态和后端一致
                await retrieveHeaderData();
                console.log("新增成功:", data);
            }
        } catch (err) {
            console.error("提交失败:", err);
        }
    };

    const updateHeaderData = async (formData) => {
        try {
            let response = await fetch("/api/update/header-data", {
                method: "PUT",
                headers: {
                    "Content-Type": "application/json",
                },
                body: JSON.stringify(formData),
            });
            let data = await response.json();
            if (response.ok) {
                // 不要用后端返回的状态对象覆盖业务数据,重新拉取最新完整数据
                await retrieveHeaderData();
                console.log("更新成功:", data);
            }
        } catch (err) {
            console.error("更新失败:", err);
        }
    }

    const contextData = {
        headerData,
        onChange,
        submitHeaderData,
        updateHeaderData,
        retrieveHeaderData
    };

    return (
        <ApiContext.Provider value={contextData}>
            {children}
        </ApiContext.Provider>
    );
}

2. Admin.js 修正

import React, { useContext, useEffect, useState } from "react";
import "./admin.css";
import ApiContext from "../../context/ApiContext";

const Admin = () => {
    const {
        onChange,
        retrieveHeaderData,
        submitHeaderData,
        updateHeaderData,
        headerData,
    } = useContext(ApiContext);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        const initData = async () => {
            await retrieveHeaderData();
            setLoading(false);
        }
        initData();
    }, []);

    const submitOrUpdateHeaderData = async (e) => {
        e.preventDefault();
        // 数据未加载完禁止提交
        if (loading || !headerData) return;
        // 统一整理表单参数
        const formParams = {
            id: headerData.id, // 传入主键ID给后端定位更新行
            title: headerData.title,
            name: headerData.name,
            linkedIn: headerData.linkedIn,
            github: headerData.github,
        };
        if (!headerData.id) {
            // 没有ID说明是新增
            await submitHeaderData(formParams);
        } else {
            await updateHeaderData(formParams);
        }
    };

    // 加载中显示提示,避免数据未就绪时表单值异常
    if (loading) return <section id="admin"><div className="container">加载中...</div></section>;

    return (
        <section id="admin">
            <h2>Change user details</h2>
            <div className="container admin__container">
                <div className="admin__forms">
                    <form onSubmit={submitOrUpdateHeaderData}>
                        <input
                            type="text"
                            name="title"
                            placeholder="Title"
                            // 加空值兜底,避免value为undefined
                            value={headerData?.title || ""}
                            onChange={(e) => onChange(e)}
                        />
                        <input
                            type="text"
                            name="name"
                            placeholder="Name"
                            value={headerData?.name || ""}
                            onChange={(e) => onChange(e)}
                        />
                        <input
                            type="text"
                            name="linkedIn"
                            placeholder="LinkedIn"
                            value={headerData?.linkedIn || ""}
                            onChange={(e) => onChange(e)}
                        />
                        <input
                            type="text"
                            name="github"
                            placeholder="Github"
                            value={headerData?.github || ""}
                            onChange={(e) => onChange(e)}
                        />
                        <button className="btn btn-primary" type="submit">
                            Save details
                        </button>
                    </form>
                </div>
            </div>
        </section>
    );
};

export default Admin;

3. server.js PUT接口修正

app.put("/api/update/header-data", async (req, res) => {
    const data = req.body;
    // 必须加WHERE条件,通过主键定位要更新的行,避免全表更新
    const updateHeaderQuery =
        "UPDATE header SET name = ?, title = ?, linkedIn = ?, github = ? WHERE id = ?";

    try {
        await database.query(updateHeaderQuery, [
            data.name,
            data.title,
            data.linkedIn,
            data.github,
            data.id // 传入主键值
        ]);
        console.log("Header Data Updated Successfully !");
        return res.json({
            status: "ok",
            message: "Updated data into header table",
        });
    } catch (e) {
        console.error(e);
        // 修正错误响应写法,不要链式调用sendStatus和json
        res.status(400).json({
            status: "error",
            message: "Error updating data into header table",
        });
    }
});
额外说明
  • 表单提交时统一传入主键ID,后端必须基于ID做更新定位,禁止无WHERE条件的UPDATE语句。
  • 接口请求统一加try/catch捕获异常,避免请求报错时页面崩溃。
  • 受控组件的value必须给兜底空字符串,禁止出现undefined作为value的情况,防止React组件受控状态切换异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 10:27:31