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

React组件间传递useState数组及编辑更新无限循环问题求助

问题说明

程序接收cards对象数组渲染卡片,点击编辑按钮可打开预填当前状态的表单,但点击保存按钮会触发无限循环,且无法将修改内容保存回原数组对应位置更新卡片显示。


原代码

Displaycards.js

import React from "react";
import ReactFitText from "react-fittext";
import Editform from "./Editform";

function Displaycards({ lastid }) {
  // dummy data hardcoded for now
  const [cards, setCards] = React.useState([
    {
      id: 1,
      gamename: "El Dorado",
      maxplayers: 4,
      freespaces: 1,
      tablenum: 5,
    },
    {
      id: 2,
      gamename: "Ticket to Ride",
      maxplayers: 4,
      freespaces: 2,
      tablenum: 3,
    },
  ]);

  const [showForm, setShowForm] = React.useState(false);
  const [currentid, setCurrentid] = React.useState(0);

  return (
    <div className="cardwrapper">
      {cards.map(({ id, gamename, maxplayers, freespaces, tablenum }) => {
        return (
          <div key={id}>
            <div>
              <div className="card">
                <ReactFitText compressor={0.8}>
                  <div className="gamename">{gamename}</div>
                </ReactFitText>
                <div className="details">
                  <p>Setup for: </p>
                  <p className="bignumbers">{maxplayers}</p>
                </div>
                <div className="details">
                  <p>Spaces free:</p>
                  <p className="bignumbers">{freespaces}</p>
                </div>
                <div className="details">
                  <p>Table #</p>
                  <p className="bignumbers">{tablenum}</p>
                </div>
                <button type="button" className="playbutton">
                  I want to play
                </button>
                <br />
              </div>

              <div className="editbuttons">
                <button
                  type="button"
                  className="editbutton"
                  onClick={() => {
                    setShowForm(!showForm);
                    setCurrentid(id);
                  }}
                >
                  Edit
                </button>
                <button type="button" className="delbutton">
                  X
                </button>
              </div>
            </div>
          </div>
        );
      })}
      {showForm && (
        <div>
          <Editform cards={cards} setCards={setCards} id={currentid} />
        </div>
      )}
    </div>
  );
}

export default Displaycards;

Editform.js

function Editform({ cards, setCards, id }) {
  const thisCard = cards.filter((card) => card.id === id)[0];
  const editThisCard = thisCard.id === id; // trying to match id of passed card to correct card in 'cards' array.
  console.log(editThisCard);

  function saveChanges(cardtochange) {
    setCards(
      cards.map(
        (
          card // intention is map back over the original array, and if the id matches that
        ) =>
          card.id === id // of the edited card, write the changed card back in at its ID
            ? {
                id: id,
                gamename: cardtochange.gamename,
                maxplayers: cardtochange.maxplayers,
                freespaces: cardtochange.freespaces,
                tablenum: cardtochange.tablenum,
              }
            : card // ... or just write the original back in place.
      )
    );
  }

  return (
    <>
      {editThisCard && ( // should only render if editThisCard is true.
        <div className="form">
          <p>Name of game:</p>
          <input type="text" value={thisCard.gamename}></input>

          <p>Max players: </p>
          <input type="text" value={thisCard.maxplayers}></input>
          <p>Free spaces: </p>
          <input type="text" value={thisCard.freespaces}></input>

          <p>Table #: </p>
          <input type="text" value={thisCard.tablenum}></input>
          <p></p>
          <button
            type="button"
            className="playbutton"
            onClick={saveChanges(thisCard)} //remove to see edit form - leave in for perpetual loop.
          >
            Save changes
          </button>
        </div>
      )}
    </>
  );
}

export default Editform;

解决方案

1. 修复无限循环问题

按钮的onClick={saveChanges(thisCard)}会在组件渲染时立即执行saveChanges,调用setCards触发父组件重新渲染,进而子组件再次渲染又执行saveChanges,形成循环。

修复方式:用箭头函数包裹,让函数仅在点击时执行:

onClick={() => saveChanges()}

2. 改造表单为受控组件

当前输入框直接绑定thisCard的值但无onChange事件,处于只读状态。需在子组件创建本地状态跟踪编辑内容:

// 在Editform组件内添加
const [editedCard, setEditedCard] = React.useState({
  gamename: thisCard.gamename,
  maxplayers: thisCard.maxplayers,
  freespaces: thisCard.freespaces,
  tablenum: thisCard.tablenum
});

// 添加输入变化处理函数
const handleInputChange = (e) => {
  const { name, value } = e.target;
  setEditedCard(prev => ({
    ...prev,
    // 数字类型字段转换为Number
    [name]: ['maxplayers', 'freespaces', 'tablenum'].includes(name) 
      ? Number(value) 
      : value
  }));
};

同时修改输入框,添加name属性和onChange绑定:

<input 
  type="text" 
  name="gamename"
  value={editedCard.gamename}
  onChange={handleInputChange}
/>

其余输入框同理修改name和绑定editedCard对应字段。

3. 正确更新父组件状态

使用函数式更新避免闭包捕获旧状态,直接用本地编辑好的editedCard替换目标卡片:

function saveChanges() {
  setCards(currCards => currCards.map(card => 
    card.id === id 
      ? { ...card, ...editedCard } // 合并原卡片与修改内容,保留id
      : card
  ));
  // 保存后关闭表单
  setShowForm(false);
}

4. 传递表单关闭方法

在父组件给Editform传递setShowForm,实现保存后自动关闭表单:

// Displaycards中修改Editform调用
{showForm && (
  <div>
    <Editform 
      cards={cards} 
      setCards={setCards} 
      id={currentid} 
      setShowForm={setShowForm} // 添加该属性
    />
  </div>
)}

5. 优化卡片查找逻辑

用find替代filter取[0],更高效简洁:

const thisCard = cards.find(card => card.id === id);

完整修改后代码

Displaycards.js

import React from "react";
import ReactFitText from "react-fittext";
import Editform from "./Editform";

function Displaycards({ lastid }) {
  const [cards, setCards] = React.useState([
    {
      id: 1,
      gamename: "El Dorado",
      maxplayers: 4,
      freespaces: 1,
      tablenum: 5,
    },
    {
      id: 2,
      gamename: "Ticket to Ride",
      maxplayers: 4,
      freespaces: 2,
      tablenum: 3,
    },
  ]);

  const [showForm, setShowForm] = React.useState(false);
  const [currentid, setCurrentid] = React.useState(0);

  return (
    <div className="cardwrapper">
      {cards.map(({ id, gamename, maxplayers, freespaces, tablenum }) => {
        return (
          <div key={id}>
            <div>
              <div className="card">
                <ReactFitText compressor={0.8}>
                  <div className="gamename">{gamename}</div>
                </ReactFitText>
                <div className="details">
                  <p>Setup for: </p>
                  <p className="bignumbers">{maxplayers}</p>
                </div>
                <div className="details">
                  <p>Spaces free:</p>
                  <p className="bignumbers">{freespaces}</p>
                </div>
                <div className="details">
                  <p>Table #</p>
                  <p className="bignumbers">{tablenum}</p>
                </div>
                <button type="button" className="playbutton">
                  I want to play
                </button>
                <br />
              </div>

              <div className="editbuttons">
                <button
                  type="button"
                  className="editbutton"
                  onClick={() => {
                    setShowForm(true);
                    setCurrentid(id);
                  }}
                >
                  Edit
                </button>
                <button type="button" className="delbutton">
                  X
                </button>
              </div>
            </div>
          </div>
        );
      })}
      {showForm && (
        <div>
          <Editform 
            cards={cards} 
            setCards={setCards} 
            id={currentid} 
            setShowForm={setShowForm} 
          />
        </div>
      )}
    </div>
  );
}

export default Displaycards;

Editform.js

import React from "react";

function Editform({ cards, setCards, id, setShowForm }) {
  const thisCard = cards.find(card => card.id === id);
  
  const [editedCard, setEditedCard] = React.useState({
    gamename: thisCard.gamename,
    maxplayers: thisCard.maxplayers,
    freespaces: thisCard.freespaces,
    tablenum: thisCard.tablenum
  });

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setEditedCard(prev => ({
      ...prev,
      [name]: ['maxplayers', 'freespaces', 'tablenum'].includes(name) 
        ? Number(value) 
        : value
    }));
  };

  function saveChanges() {
    setCards(currCards => currCards.map(card => 
      card.id === id 
        ? { ...card, ...editedCard }
        : card
    ));
    setShowForm(false);
  }

  return (
    <div className="form">
      <p>Name of game:</p>
      <input 
        type="text" 
        name="gamename"
        value={editedCard.gamename}
        onChange={handleInputChange}
      />

      <p>Max players: </p>
      <input 
        type="text" 
        name="maxplayers"
        value={editedCard.maxplayers}
        onChange={handleInputChange}
      />
      
      <p>Free spaces: </p>
      <input 
        type="text" 
        name="freespaces"
        value={editedCard.freespaces}
        onChange={handleInputChange}
      />

      <p>Table #: </p>
      <input 
        type="text" 
        name="tablenum"
        value={editedCard.tablenum}
        onChange={handleInputChange}
      />
      
      <p></p>
      <button
        type="button"
        className="playbutton"
        onClick={saveChanges}
      >
        Save changes
      </button>
    </div>
  );
}

export default Editform;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 01:50:32