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

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;

问题分析

  1. 多次触发状态更新:copiarDatos中用map循环调用setDatosPorCopiar,会导致多次状态更新,不仅效率低,还可能引发不必要的渲染。
  2. 异步操作未正确处理:mapDatosPorCopiar中用map处理异步函数,map不会等待异步操作完成,无法保证所有数据都成功保存。
  3. 组件渲染依赖未更新:表格基于data props渲染,数据保存到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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 14:30:52