React中点击Test按钮组件不更新问题求助
React组件点击按钮后未更新的问题修复
问题描述
我有一个名为TableConsolidado的React组件,点击Test按钮会触发copiarDatos函数,该函数通过setDatosPorCopiar更新datosPorCopiar状态;同时用useEffect监听该状态变化,状态改变时执行mapDatosPorCopiar将数据保存至Firestore。目前点击按钮后组件并未更新,需要实现点击按钮后组件正常更新。
原问题代码
const TableConsolidado = ({ confirmDelete, data, names, anio, mes }) => { const [datosPorCopiar, setDatosPorCopiar] = useState([]); const [helper, setHelper] = useState(false); const copiarDatos = () => { const meses = [ "diciembre", "enero", "febrero", ]; let mesAnterior; let anioNuevo = anio; if (mes === "diciembre") { // si el caso es diciembre, entonces el anterior es noviembre. mesAnterior = "noviembre"; } else { // en cualquier otro caso, el mes anterior es el que se encuentra una posición antes en el arreglo de meses. const indiceMesActual = meses.indexOf(mes); mesAnterior = meses[indiceMesActual - 1]; } if (mesAnterior === "diciembre") { anioNuevo = anio - 1; } data .filter((dat) => dat.mes === mesAnterior && dat.anio === anioNuevo) .map((da) => setDatosPorCopiar((prevState) => [ ...prevState, { anio: da.anio, dni: da.dni, id: da.id, lastName: da.lastName, mes: da.mes, montoPagado: da.montoPagado, name: da.name, notas: da.notas, phone: da.phone, price: da.price, roomName: da.roomName, }, ]) ); console.log("se ejecuto copiar datos (pasar datos al state)"); }; const mapDatosPorCopiar = async () => { datosPorCopiar.map(async (datos) => { await addDoc(consolidadosCollection, { dni: datos.dni, anio: datos.anio, mes: mes, lastName: datos.lastName, name: datos.name, phone: datos.phone, price: datos.price, roomName: datos.roomName, }); console.log("se guardaron los datos en la bd"); }); }; useEffect(() => { if (datosPorCopiar.length > 0) { console.log("ingreso al use effect y ejecuto"); mapDatosPorCopiar(); } else { console.log("ingreso pero no ejecuto"); } }, [datosPorCopiar]); return ( <> <hr /> <div className="container"> <div className="row"> <div className="col"> <div className="d-grid gap-2"> <Link to="/createconsolidado" state={{ names: names, anio: anio, mes: mes }} className="btn btn-secondary mt-2 mb-2" > CREATE </Link> </div> <table className="table table-dark table-hover"> <thead> <tr> <th>CUARTO</th> <th>DNI</th> <th>NOMBRES</th> <th>APELLIDOS</th> <th>TELEFONO</th> <th>PRECIO</th> <th>MONTO PAGADO</th> <th>NOTAS</th> <th>ACCION</th> </tr> </thead> <tbody> {data .filter((dat) => dat.mes === mes && dat.anio === anio) .map((dat) => ( <tr key={dat.id}> <td>{dat.roomName}</td> <td>{dat.dni}</td> <td>{dat.name}</td> <td>{dat.lastName}</td> <td>{dat.phone}</td> <td>{dat.price}</td> <td>{dat.montoPagado}</td> <td>{dat.notas}</td> <td> <Link to={`/edit/${dat.id}`} state={{ names: names }} className="btn btn-light" > <i className="fa-solid fa-pencil"></i> </Link> <button onClick={() => { confirmDelete(dat.id); }} className="btn btn-danger" > <i className="fa-solid fa-trash"></i> </button> </td> </tr> ))} </tbody> </table> <button onClick={copiarDatos} value={mes}> Test </button> </div> </div> </div> </> ); }; export default TableConsolidado;
问题分析
- 多次触发状态更新:
copiarDatos中用map循环调用setDatosPorCopiar,会导致多次状态更新,不仅效率低,还可能引发不必要的渲染。 - 异步操作未正确处理:
mapDatosPorCopiar中用map处理异步函数,map不会等待异步操作完成,无法保证所有数据都成功保存。 - 组件渲染依赖未更新:表格基于
dataprops渲染,数据保存到Firestore后data未更新,导致组件无法展示新数据。
修复方案
1. 优化状态更新逻辑
先一次性处理好所有要复制的数据,再批量更新状态,避免多次调用setDatosPorCopiar:
const copiarDatos = () => { const meses = ["diciembre", "enero", "febrero"]; let mesAnterior; let anioNuevo = anio; if (mes === "diciembre") { mesAnterior = "noviembre"; } else { const indiceMesActual = meses.indexOf(mes); mesAnterior = meses[indiceMesActual - 1]; } if (mesAnterior === "diciembre") { anioNuevo = anio - 1; } // 批量处理数据,一次性更新状态 const filteredData = data .filter((dat) => dat.mes === mesAnterior && dat.anio === anioNuevo) .map((da) => ({ anio: da.anio, dni: da.dni, id: da.id, lastName: da.lastName, mes: da.mes, montoPagado: da.montoPagado, name: da.name, notas: da.notas, phone: da.phone, price: da.price, roomName: da.roomName, })); setDatosPorCopiar(filteredData); console.log("se ejecuto copiar datos (pasar datos al state)"); };
2. 正确处理异步批量操作
用Promise.all等待所有Firestore保存操作完成,确保数据全部写入:
const mapDatosPorCopiar = async () => { await Promise.all( datosPorCopiar.map(async (datos) => { await addDoc(consolidadosCollection, { dni: datos.dni, anio: anio, // 注意这里应该用当前组件的anio,而非原数据的anio mes: mes, // 同理,用当前组件的mes lastName: datos.lastName, name: datos.name, phone: datos.phone, price: datos.price, roomName: datos.roomName, }); console.log("se guardaron los datos en la bd"); }) ); // 通知父组件刷新数据(需要父组件传入回调函数,比如refreshData) // 示例:if (props.refreshData) props.refreshData(); };
3. 更新组件渲染依赖
组件表格基于data props渲染,数据保存完成后,需要让父组件重新从Firestore获取最新数据,更新data props,这样组件会自动重新渲染展示新数据。
修复后的完整代码
const TableConsolidado = ({ confirmDelete, data, names, anio, mes, refreshData }) => { const [datosPorCopiar, setDatosPorCopiar] = useState([]); const copiarDatos = () => { const meses = ["diciembre", "enero", "febrero"]; let mesAnterior; let anioNuevo = anio; if (mes === "diciembre") { mesAnterior = "noviembre"; } else { const indiceMesActual = meses.indexOf(mes); mesAnterior = meses[indiceMesActual - 1]; } if (mesAnterior === "diciembre") { anioNuevo = anio - 1; } const filteredData = data .filter((dat) => dat.mes === mesAnterior && dat.anio === anioNuevo) .map((da) => ({ anio: da.anio, dni: da.dni, id: da.id, lastName: da.lastName, mes: da.mes, montoPagado: da.montoPagado, name: da.name, notas: da.notas, phone: da.phone, price: da.price, roomName: da.roomName, })); setDatosPorCopiar(filteredData); console.log("se ejecuto copiar datos (pasar datos al state)"); }; const mapDatosPorCopiar = async () => { await Promise.all( datosPorCopiar.map(async (datos) => { await addDoc(consolidadosCollection, { dni: datos.dni, anio: anio, mes: mes, lastName: datos.lastName, name: datos.name, phone: datos.phone, price: datos.price, roomName: datos.roomName, }); console.log("se guardaron los datos en la bd"); }) ); // 刷新父组件数据,触发当前组件更新 if (refreshData) refreshData(); // 清空状态,避免重复执行 setDatosPorCopiar([]); }; useEffect(() => { if (datosPorCopiar.length > 0) { console.log("ingreso al use effect y ejecuto"); mapDatosPorCopiar(); } }, [datosPorCopiar]); return ( <> <hr /> <div className="container"> <div className="row"> <div className="col"> <div className="d-grid gap-2"> <Link to="/createconsolidado" state={{ names: names, anio: anio, mes: mes }} className="btn btn-secondary mt-2 mb-2" > CREATE </Link> </div> <table className="table table-dark table-hover"> <thead> <tr> <th>CUARTO</th> <th>DNI</th> <th>NOMBRES</th> <th>APELLIDOS</th> <th>TELEFONO</th> <th>PRECIO</th> <th>MONTO PAGADO</th> <th>NOTAS</th> <th>ACCION</th> </tr> </thead> <tbody> {data .filter((dat) => dat.mes === mes && dat.anio === anio) .map((dat) => ( <tr key={dat.id}> <td>{dat.roomName}</td> <td>{dat.dni}</td> <td>{dat.name}</td> <td>{dat.lastName}</td> <td>{dat.phone}</td> <td>{dat.price}</td> <td>{dat.montoPagado}</td> <td>{dat.notas}</td> <td> <Link to={`/edit/${dat.id}`} state={{ names: names }} className="btn btn-light" > <i className="fa-solid fa-pencil"></i> </Link> <button onClick={() => { confirmDelete(dat.id); }} className="btn btn-danger" > <i className="fa-solid fa-trash"></i> </button> </td> </tr> ))} </tbody> </table> <button onClick={copiarDatos} value={mes}> Test </button> </div> </div> </div> </> ); }; export default TableConsolidado;
内容的提问来源于stack exchange,提问作者Victor
相关产品推荐
相关产品推荐

