React - Dynamically render data into accordion after API call

Viewed 92

I am developing a management React application. In the application, I have a table (MaterialTable) in which I show the detail for each row with the detailPanel option provided. Inside the detail panel I have three accordions, the first populated with rowData of the table, while the other two must be populated following an API call.

But I have the following problem: when I open the detail of a row (example row number 3) and then click on the second or third accordion, data is not retrieved correctly. By clicking on another row (example row 5), the data is correctly retreived and shown, but it is not the data relating to row 5, but those relating to row 3.

I therefore always have one step late in updating the accordion.

Do you have any suggestions or advice?

  • myCode:
import MaterialTable from "material-table";
import React, { useEffect, useState } from "react";
import tableIcons from "../templates/TableIcons";
import getColumns from "./Tbl_DocumentoColumns";
import Edit from "@material-ui/icons/Edit";
import {
  Accordion,
  AccordionSummary,
  AccordionDetails,
} from "@material-ui/core";
import { Switch } from "@material-ui/core";
import { withRouter } from "react-router";
import { AddBox } from "@material-ui/icons";
import ArrowDownwardOutlinedIcon from "@material-ui/icons/ArrowDownwardOutlined";
import {
  deleteTbl_Documento,
  getTbl_Documento,
} from "../../repo/tbl_documentoRepo";
import { searchRata } from "../../repo/tbl_rataRepo";
import { searchDettaglioRata } from "../../repo/tbl_dettagli_rataRepo";
import { Loading } from "../templates/Loading";
/*
Documentation on developing the Material-Table can be found at https://material-table.com/
*/

const Tbl_DocumentoTable = (props) => {
  const history = props.history;
  const [columns, setColumns] = useState(getColumns({}));
  //const [rata, setAccordionRata] = useState({idDoc: ""});
  //const [drata, setAccordionDetailRata] = useState({idDoc: ""});
  const [loading, setLoading] = useState(false);
  const [rata, setIdDocR] = useState({ iddoc: "" });
  const [drata, setIdDocDR] = useState({ iddoc: "" });
  const [acc_rata, setAccordionR] = useState([]);
  const [acc_drata, setAccordionDR] = useState([]);
  //Here we call delete
  const handleRowDelete = (oldData, resolve) => {
    setLoading(true);
    deleteTbl_Documento(oldData.idDoc)
      .then((res) => {
        resolve();
        setLoading(false);
      })
      .catch((error) => {
        resolve();
        setLoading(false);
      });
  };
  //Settiamo l'idDoc di riferimento per la rata
  const handleRata = (evt) => {
    setIdDocR({ iddoc: evt });
  };
  //Settiamo l'idDoc di riferimento per il dettaglio rata
  const handleDettagliRata = (evt) => {
    setIdDocDR({ iddoc: evt });
  };

  useEffect(() => {
    const R = async () => {
      const r = await searchRata(rata.iddoc);
      setAccordionR(r.data.data.data);
    };
    R();
    console.log(acc_rata);
  }, [rata.iddoc]);

  useEffect(() => {
    const DR = async () => {
      const r = await searchDettaglioRata(drata.iddoc);
      setAccordionDR(r.data.data.data);
    };
    DR();
    console.log(acc_drata);
  }, [drata.iddoc]);

  return (
    <div>
      <MaterialTable
        style={{
          marginLeft: 30,
          responsive: true,
          marginTop: 50,
          marginRight: 30,
        }}
        minRows={15}
        title="Lista dei documenti"
        columns={columns}
        data={async (query) => {
          setLoading(true);
          const res = await getTbl_Documento(
            query.page,
            query.pageSize,
            query.search
          );
          setLoading(false);
          return {
            data: res.data,
            page: query.page,
            totalCount: parseInt(res.total),
          };
        }}
        detailPanel={[
          {
            tooltip: "Dettaglio",
            icon: "+",
            openIcon: "-",
            render: (rowData) => {
              return (
                <div>
                  <Accordion>
                    <AccordionSummary
                      expandIcon={<ArrowDownwardOutlinedIcon />}
                      aria-controls="panel1a-content"
                      id="panel1a-header"
                    >
                      Dettaglio del documento
                    </AccordionSummary>
                    <AccordionDetails>
                      <div
                        style={{
                          columns: 3,
                          width: "100%",
                          responsive: true,
                        }}
                      >
                        <ul style={{ fontSize: "14px" }}>
                          <li>Catena Servizio: {rowData.idCatenaServizio}</li>
                          <li>Commessa: {rowData.idCommessa}</li>
                          <li>Spedizione Racc: {rowData.spedRacc}</li>
                          <li>Ricezione Sped: {rowData.spedRic}</li>
                          <li>Nome Documento: {rowData.DocNomeFile}</li>
                          <li>
                            Nome Documento Cliente: {rowData.DocNomeFileCliente}
                          </li>
                          <li>
                            ID Cliente sul documento: {rowData.docIDCliente}
                          </li>
                          <li>#Documento: {rowData.docNum}</li>
                          <li>Data documento: {rowData.docData}</li>
                          <li>Destinatario 2: {rowData.docDestinatario2}</li>
                          <li>Destinatario 3: {rowData.docDestinatario3}</li>
                          <li>Destinatario 4: {rowData.docDestinatario4}</li>
                          <li>Destinatario 5: {rowData.docDestinatario5}</li>
                          <li>Destinatario 6: {rowData.docDestinatario6}</li>
                          <li>Codice Avvio: {rowData.codiceAvvio}</li>
                          <li>Dati 1: {rowData.dati1}</li>
                          <li>Dati 2: {rowData.dati2}</li>
                          <li>Dati 3: {rowData.dati3}</li>
                          <li>Dati 4: {rowData.dati4}</li>
                          <li>Dati 5: {rowData.dati5}</li>
                          <li>Spedito: {rowData.spedito}</li>
                          <li>Nome Immagine: {rowData.nomeImmagine}</li>
                          <li>
                            Pagamento abilitato: {rowData.pagamentoAbilitato}
                          </li>
                          <li>UltimaModifica: {rowData.lastUpdate}</li>
                        </ul>
                      </div>
                    </AccordionDetails>
                  </Accordion>
                  <Accordion>
                    <AccordionSummary
                      expandIcon={<ArrowDownwardOutlinedIcon />}
                      aria-controls="panel1a-content"
                      id="panel1a-header"
                      onClickCapture={() => handleRata(rowData.idDoc)}
                    >
                      Rate associate
                    </AccordionSummary>
                    <AccordionDetails data={acc_rata}>
                      <div
                        style={{
                          columns: 3,
                          width: "100%",
                        }}
                      >
                        <ul style={{ fontSize: "14px" }}>
                          {acc_rata.map((y) => {
                            console.log(y.idBol);
                            return <li key={y.idBol}>{y.idBol}</li>;
                          })}
                        </ul>
                      </div>
                    </AccordionDetails>
                  </Accordion>
                  <Accordion>
                    <AccordionSummary
                      expandIcon={<ArrowDownwardOutlinedIcon />}
                      aria-controls="panel1a-content"
                      id="panel1a-header"
                      onClickCapture={() => handleDettagliRata(rowData.idDoc)}
                    >
                      Dettaglio delle rate
                    </AccordionSummary>
                    <AccordionDetails data={acc_drata}>
                      <div
                        style={{
                          columns: 3,
                          width: "100%",
                        }}
                      >
                        <ul style={{ fontSize: "14px" }}>
                          {acc_drata.map((adr) => {
                            return <li key={adr.idBol}>idBol: {adr.idBol}</li>;
                          })}
                        </ul>
                      </div>
                    </AccordionDetails>
                  </Accordion>
                </div>
              );
            },
          },
        ]}
        options={{
          sorting: true,
          actionsColumnIndex: -1,
          pageSize: 20,
          toolbar: true,
          paging: true,
          responsive: true,
          exportButton: true,
          exportAllData: true,
          exportFileName: "ListaTributi",
        }}
        onRowClick={(event, rowData, togglePanel) => togglePanel()}
        actions={[
          {
            icon: () => <Edit />,
            tooltip: "Edit",
            onClick: (event, rowData) => {
              history.push({
                pathname: `/tbl_documento/update/${rowData.idDoc}`,
                user: rowData,
              });
            },
          },
          {
            icon: () => <AddBox variant="contained" color="secondary" />,
            tooltip: "Add New",
            // This makes add button to appear in table toolbar instead for each row
            isFreeAction: true,
            onClick: (event, rowData) => {
              history.push("/tbl_documento/add");
            },
          },
        ]}
        icons={tableIcons}
        editable={{
          onRowDelete: (oldData) =>
            new Promise((resolve) => {
              handleRowDelete(oldData, resolve);
            }),
        }}
      />
    </div>
  );
};
export default withRouter(Tbl_DocumentoTable);
1 Answers

You're mixing several concerns in one single component. Please revisit the docs to think in react. That said, you could encapsulate the rendering logic of the DetailPanel in a component with an useEffect hook to load both kinds of data, Rata and DettaglioRata, and make all that logic depends on the rowData only.

const DetailPanel = ({ rowData }) => {
  const [acc_rata, setAccordionR] = useState([]);
  const [acc_drata, setAccordionDR] = useState([]);
  useEffect(() => {
    const loadData = async () => {
      const r = await searchRata(rowData.iddoc);
      const dr = await searchDettaglioRata(rowData.iddoc);
      setAccordionR(r.data.data.data);
      setAccordionDR(dr.data.data.data);
    };
    loadData();
  }, [rowData.iddoc]);

  return (
    <div>
      <Accordion>
        <AccordionSummary
          expandIcon={<ArrowDownwardOutlinedIcon />}
          aria-controls="panel1a-content"
          id="panel1a-header"
        >
          Dettaglio del documento
        </AccordionSummary>
        <AccordionDetails>
          <div
            style={{
              columns: 3,
              width: "100%",
              responsive: true,
            }}
          >
            <ul style={{ fontSize: "14px" }}>
              <li>Catena Servizio: {rowData.idCatenaServizio}</li>
              <li>Commessa: {rowData.idCommessa}</li>
              <li>Spedizione Racc: {rowData.spedRacc}</li>
              <li>Ricezione Sped: {rowData.spedRic}</li>
              <li>Nome Documento: {rowData.DocNomeFile}</li>
              <li>Nome Documento Cliente: {rowData.DocNomeFileCliente}</li>
              <li>ID Cliente sul documento: {rowData.docIDCliente}</li>
              <li>#Documento: {rowData.docNum}</li>
              <li>Data documento: {rowData.docData}</li>
              <li>Destinatario 2: {rowData.docDestinatario2}</li>
              <li>Destinatario 3: {rowData.docDestinatario3}</li>
              <li>Destinatario 4: {rowData.docDestinatario4}</li>
              <li>Destinatario 5: {rowData.docDestinatario5}</li>
              <li>Destinatario 6: {rowData.docDestinatario6}</li>
              <li>Codice Avvio: {rowData.codiceAvvio}</li>
              <li>Dati 1: {rowData.dati1}</li>
              <li>Dati 2: {rowData.dati2}</li>
              <li>Dati 3: {rowData.dati3}</li>
              <li>Dati 4: {rowData.dati4}</li>
              <li>Dati 5: {rowData.dati5}</li>
              <li>Spedito: {rowData.spedito}</li>
              <li>Nome Immagine: {rowData.nomeImmagine}</li>
              <li>Pagamento abilitato: {rowData.pagamentoAbilitato}</li>
              <li>UltimaModifica: {rowData.lastUpdate}</li>
            </ul>
          </div>
        </AccordionDetails>
      </Accordion>
      <Accordion>
        <AccordionSummary
          expandIcon={<ArrowDownwardOutlinedIcon />}
          aria-controls="panel1a-content"
          id="panel1a-header"
        >
          Rate associate
        </AccordionSummary>
        <AccordionDetails data={acc_rata}>
          <div
            style={{
              columns: 3,
              width: "100%",
            }}
          >
            <ul style={{ fontSize: "14px" }}>
              {acc_rata.map((y) => {
                return <li key={y.idBol}>{y.idBol}</li>;
              })}
            </ul>
          </div>
        </AccordionDetails>
      </Accordion>
      <Accordion>
        <AccordionSummary
          expandIcon={<ArrowDownwardOutlinedIcon />}
          aria-controls="panel1a-content"
          id="panel1a-header"
        >
          Dettaglio delle rate
        </AccordionSummary>
        <AccordionDetails data={acc_drata}>
          <div
            style={{
              columns: 3,
              width: "100%",
            }}
          >
            <ul style={{ fontSize: "14px" }}>
              {acc_drata.map((adr) => {
                return <li key={adr.idBol}>idBol: {adr.idBol}</li>;
              })}
            </ul>
          </div>
        </AccordionDetails>
      </Accordion>
    </div>
  );
};

That would clean up a little your main component

const Tbl_DocumentoTable = (props) => {
  const history = props.history;
  const [columns, setColumns] = useState(getColumns({}));
  //const [rata, setAccordionRata] = useState({idDoc: ""});
  //const [drata, setAccordionDetailRata] = useState({idDoc: ""});
  const [loading, setLoading] = useState(false);
  //Here we call delete
  const handleRowDelete = (oldData, resolve) => {
    setLoading(true);
    deleteTbl_Documento(oldData.idDoc)
      .then((res) => {
        resolve();
        setLoading(false);
      })
      .catch((error) => {
        resolve();
        setLoading(false);
      });
  };

  return (
    <div>
      <MaterialTable
        style={{
          marginLeft: 30,
          responsive: true,
          marginTop: 50,
          marginRight: 30,
        }}
        minRows={15}
        title="Lista dei documenti"
        columns={columns}
        data={async (query) => {
          setLoading(true);
          const res = await getTbl_Documento(
            query.page,
            query.pageSize,
            query.search
          );
          setLoading(false);
          return {
            data: res.data,
            page: query.page,
            totalCount: parseInt(res.total),
          };
        }}
        detailPanel={[
          {
            tooltip: "Dettaglio",
            icon: "+",
            openIcon: "-",
            render: (rowData) => {
              return <DetailPanel rowData={rowData} />;
            },
          },
        ]}
        options={{
          sorting: true,
          actionsColumnIndex: -1,
          pageSize: 20,
          toolbar: true,
          paging: true,
          responsive: true,
          exportButton: true,
          exportAllData: true,
          exportFileName: "ListaTributi",
        }}
        onRowClick={(event, rowData, togglePanel) => togglePanel()}
        actions={[
          {
            icon: () => <Edit />,
            tooltip: "Edit",
            onClick: (event, rowData) => {
              history.push({
                pathname: `/tbl_documento/update/${rowData.idDoc}`,
                user: rowData,
              });
            },
          },
          {
            icon: () => <AddBox variant="contained" color="secondary" />,
            tooltip: "Add New",
            // This makes add button to appear in table toolbar instead for each row
            isFreeAction: true,
            onClick: (event, rowData) => {
              history.push("/tbl_documento/add");
            },
          },
        ]}
        icons={tableIcons}
        editable={{
          onRowDelete: (oldData) =>
            new Promise((resolve) => {
              handleRowDelete(oldData, resolve);
            }),
        }}
      />
    </div>
  );
};
export default withRouter(Tbl_DocumentoTable);

The issue you're experiencing it's probably a gap created when updating several states and loading data via useEffect in the same component.

Related