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
相关产品推荐
相关产品推荐

