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

如何在React中使用useState Hook更新嵌套对象的单个值

解决React嵌套对象状态更新问题

问题核心

你的代码存在几个关键问题导致无法正常更新嵌套状态:

  • 输入框仅标记min/max,无法区分所属的父级约束对象,状态更新时找不到正确的嵌套层级
  • 状态更新逻辑错误,直接用输入值覆盖了父级对象,破坏了原有的嵌套结构
  • 未处理输入值的类型转换(输入为字符串,需转为数字)
  • 取消、更新按钮未实现对应逻辑

修改方案与完整代码

import { useEffect, useState } from "react";
import "./styles.css";

export default function App() {
  const [insightsData, setInsightsData] = useState({
    LeadtimePhysicalConstraints: { min: null, max: null },
    LeadtimeMgmntConstraints: { min: null, max: null },
    RFIReviewResponse: { min: null, max: null },
    ChangeOrderIssueReview: { min: null, max: null }
  });

  // 保存初始API数据,用于取消按钮重置
  const [initialInsightsData, setInitialInsightsData] = useState({});

  const apiResponse = [
    {
      LeadtimePhysicalConstraints: { min: null, max: null },
      LeadtimeMgmntConstraints: { min: 3, max: 5 },
      RFIReviewResponse: { min: 6, max: 10 },
      ChangeOrderIssueReview: { min: 8, max: 11 }
    }
  ];

  useEffect(() => {
    const initialData = apiResponse[0];
    setInsightsData(initialData);
    setInitialInsightsData(initialData);
  }, []);

  const handleInsightsChange = (e) => {
    const { name, value } = e.target;
    // 拆分父对象键和子属性键
    const [parentKey, childKey] = name.split(".");
    // 转换为数字,空值设为null
    const numValue = value === "" ? null : Number(value);

    setInsightsData((prevState) => ({
      ...prevState,
      [parentKey]: {
        ...prevState[parentKey],
        [childKey]: numValue
      }
    }));
  };

  const updateInsights = () => {
    // 这里可添加提交到后端API的逻辑
    console.log("提交更新的数据:", insightsData);
  };

  const resetInsights = () => {
    // 重置为初始API返回的数据
    setInsightsData(initialInsightsData);
  };

  return (
    <div className="App">
      <div className="container">
        <h3> Insights Settings Metadata</h3>
        <div className="constraint-info">
          <div className="input-area">
            <label className="label">Physical constraint: </label>
            <div className="input-boxes">
              <input
                className="input-box"
                value={insightsData.LeadtimePhysicalConstraints.min ?? ""}
                placeholder="min"
                onChange={handleInsightsChange}
                name="LeadtimePhysicalConstraints.min"
                type="number"
              />
              <input
                className="input-box"
                value={insightsData.LeadtimePhysicalConstraints.max ?? ""}
                placeholder="max"
                onChange={handleInsightsChange}
                name="LeadtimePhysicalConstraints.max"
                type="number"
              />
            </div>
          </div>

          <div className="input-area">
            <label className="label">LeadtimeMgmnt constraint: </label>
            <div className="input-boxes">
              <input
                className="input-box"
                value={insightsData.LeadtimeMgmntConstraints.min ?? ""}
                placeholder="min"
                onChange={handleInsightsChange}
                name="LeadtimeMgmntConstraints.min"
                type="number"
              />
              <input
                className="input-box"
                value={insightsData.LeadtimeMgmntConstraints.max ?? ""}
                placeholder="max"
                onChange={handleInsightsChange}
                name="LeadtimeMgmntConstraints.max"
                type="number"
              />
            </div>
          </div>

          <div className="input-area">
            <label className="label">RFIReviewResponse: </label>
            <div className="input-boxes">
              <input
                className="input-box"
                value={insightsData.RFIReviewResponse.min ?? ""}
                placeholder="min"
                onChange={handleInsightsChange}
                name="RFIReviewResponse.min"
                type="number"
              />
              <input
                className="input-box"
                value={insightsData.RFIReviewResponse.max ?? ""}
                placeholder="max"
                onChange={handleInsightsChange}
                name="RFIReviewResponse.max"
                type="number"
              />
            </div>
          </div>

          <div className="input-area">
            <label className="label">ChangeOrderIssueReview constraint: </label>
            <div className="input-boxes">
              <input
                className="input-box"
                value={insightsData.ChangeOrderIssueReview.min ?? ""}
                placeholder="min"
                onChange={handleInsightsChange}
                name="ChangeOrderIssueReview.min"
                type="number"
              />
              <input
                className="input-box"
                value={insightsData.ChangeOrderIssueReview.max ?? ""}
                placeholder="max"
                onChange={handleInsightsChange}
                name="ChangeOrderIssueReview.max"
                type="number"
              />
            </div>
          </div>

          <div className="action-btns">
            <button className="cancel-btn" onClick={resetInsights}>Cancel</button>
            <button className="update-btn" onClick={updateInsights}>Update</button>
          </div>
        </div>
      </div>
    </div>
  );
}

关键修改说明

  1. 输入框标识优化:给每个输入框的name设置为父对象名.min/父对象名.max格式,明确指定要更新的嵌套属性
  2. 状态更新逻辑修复:拆分name属性定位到嵌套层级,通过展开运算符逐层更新状态,避免破坏原有结构
  3. 类型处理:将输入的字符串转为数字,空输入转为null,与初始状态格式保持一致
  4. 按钮功能实现:取消按钮重置为初始API数据,更新按钮可添加提交逻辑
  5. 输入体验优化:添加type="number"限制输入类型,用?? ""避免null显示为字符串"null"

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 03:50:39