React.js using react-table v7. table does not update after state change

724 Views Asked by At

i'm creating a table where once you click a row, a popup modal opens up allowing you to update the content of the row. the modal's closing sequence updates firestore of the changes and also activates a props function which updates the changes into the above component.

closing the modal does not update the table (I've come to realize it does'nt cause a component re-reder at all...)

components:

father to the table:

import React, { Component } from "react";
import { db } from "../../firebase/firebase";
import InventoryTable from "./Table/InventoryTable";

class InventoryPage extends Component {
  constructor(props) {
    super(props);

    this.state = {
      filterGroups: [],
      data: [],
      originalData: [],
    };
    this.modalUpdate = this.modalUpdate.bind(this);
    this.filterGroupToggle = this.filterGroupToggle.bind(this);
  }

  modalUpdate(modifiedRow) {
    let newData = this.state.data;
    let oldRow = newData.filter((row) => {
      return row.id === modifiedRow.id;
    });
    let oldRowIndex = newData.indexOf(...oldRow);
    newData.splice(oldRowIndex, 1, modifiedRow);
    console.log(newData);
    this.setState({
      data: newData,
    });
  }

  filterGroupToggle(index) {
    // Switch the groupFilter that was clicked to ON/OFF
    let newFilterGroups = this.state.filterGroups;
    newFilterGroups[index].on = !newFilterGroups[index].on;
    this.setState({
      filterGroups: newFilterGroups,
    });
    const included = this.state.filterGroups
      .filter((group) => {
        return group.on;
      })
      .map((group) => {
        return group.name;
      });
    if (included.length) {
      console.log(included);
      let newData = this.state.data;
      newData = newData.filter((row) => {
        return included.includes(row.group);
      });
      this.setState({
        data: newData,
      });
    } else {
      this.setState({
        data: this.state.originalData,
      });
    }
  }

  componentDidMount() {
    db.collection("inventoryData")
      .doc("filtering")
      .get()
      .then((doc) => {
        let newFilterGroups = [];
        doc.data().groups.map((group) => {
          newFilterGroups.push({
            name: group,
            on: false,
          });
          return this.setState({
            filterGroups: newFilterGroups,
          });
        });
      });
    db.collection("inventory")
      .get()
      .then((quertSnapshot) => {
        let data = [];
        quertSnapshot.forEach((doc) => {
          data.push({
            id: doc.id,
            ...doc.data(),
          });
        });
        this.setState({
          data,
          originalData: data,
        });
      });
  }

  render() {
    console.log("InventoryPage render");
    const { filterGroups } = this.state;
    return (
      <>
        <div className="container">
          <div className="h1 mb-4 mt-2">Inventory</div>
          {filterGroups.length ? (
            <div className="row justify-content-around">
              {filterGroups.map((group, index) => {
                return (
                  <div
                    key={index}
                    className={
                      "col-2 mx-1 mb-3 toggle-buttons " +
                      (group.on ? "ON" : null)
                    }
                    onClick={() => {
                      this.filterGroupToggle(index);
                    }}
                  >
                    {group.name}
                  </div>
                );
              })}
            </div>
          ) : (
            <div className="h4">Loading Filtering Groups...</div>
          )}
        </div>
        {this.state.data.length ? (
          <InventoryTable db={this.state.data} modalUpdate={this.modalUpdate} />
        ) : (
          <div className="container h4 mt-3">No items to show...</div>
        )}
      </>
    );
  }
}

export default InventoryPage;

Table component:

import React, { useState, useEffect } from "react";

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSortUp, faSortDown } from "@fortawesome/free-solid-svg-icons";

import { useTable, useSortBy, useGlobalFilter } from "react-table";

import ManualEditModal from "./ManualEditModal";
import GlobalFilter from "./GlobalFilter";

const InventoryTable = ({ db, modalUpdate }) => {
  const [modalData, setModalData] = useState(null);
  const [showModal, setShowModal] = useState(false);

  useEffect(() => {
    console.log("modal data effect");
    if (modalData) {
      setShowModal(true);
    } else {
      setShowModal(false);
    }
  }, [modalData]);

  const handleCloseModal = () => {
    console.log("close modal");
    // setShowModal(false);
    setModalData(null);
  };

  // i'm supposed to use useMemo here,
  // but i couldn't find a way to rerender the table
  // upon props change aside from deleting useMemo
  const data = React.useMemo(db, []);
  const columns = React.useMemo(
    () => [
      {
        Header: "Info",
        columns: [
          {
            Header: "Name",
            accessor: "name", // accessor is the "key" in the data
          },
          {
            Header: "Type",
            accessor: "type",
          },
        ],
      },
      {
        Header: "Stock",
        columns: [
          {
            Header: "Current Stock",
            accessor: "stock",
          },
          {
            Header: "Awaiting Arrival",
            accessor: "await_arrival",
          },
          {
            Header: "Awaiting Order",
            accessor: "await_order",
          },
        ],
      },
    ],
    []
  );

  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
    state,
    setGlobalFilter,
  } = useTable({ columns, data }, useGlobalFilter, useSortBy);
  const { globalFilter } = state;

  return (
    <div className="container mt-3">
      {showModal ? (
        <div className="editRowModal">
          <ManualEditModal
            show={showModal}
            data={modalData}
            onHide={handleCloseModal}
            modalUpdate={modalUpdate}
          />
        </div>
      ) : null}

      <div className="form-group mb-3">
        <GlobalFilter filter={globalFilter} setFilter={setGlobalFilter} />
      </div>
      <table {...getTableProps()} className="table table-bordered">
        <thead className="thead-dark">
          {headerGroups.map((headerGroup) => (
            <tr {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.map((column) => (
                <th
                  {...column.getHeaderProps(column.getSortByToggleProps())}
                  scope="col"
                >
                  {column.render("Header")}
                  <span className="sorting-icon">
                    {column.isSorted ? (
                      column.isSortedDesc ? (
                        <FontAwesomeIcon icon={faSortUp} className="ml-2" />
                      ) : (
                        <FontAwesomeIcon icon={faSortDown} className="ml-2" />
                      )
                    ) : (
                      ""
                    )}
                  </span>
                </th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody {...getTableBodyProps()}>
          {rows.map((row) => {
            prepareRow(row);
            return (
              <tr
                {...row.getRowProps()}
                onClick={() => {
                  setModalData(row.original);
                }}
              >
                {row.cells.map((cell) => {
                  return (
                    <td {...cell.getCellProps()}>{cell.render("Cell")}</td>
                  );
                })}
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
};

export default InventoryTable;

modal Component:

import React, { useState } from "react";
import { Modal, Button } from "react-bootstrap";
import { db } from "../../../firebase/firebase";

const ManualEditModal = ({ show, data, onHide, modalUpdate }) => {
  const [stock, setStock] = useState(data ? data.stock : -1);

  // update the database on the new stock value
  const onSave = () => {
    if (isNaN(Number(stock)) || stock < 0) {
      alert("Invalid value");
      return;
    }
    db.collection("inventory")
      .doc(data.id)
      .update({
        stock: Number(stock),
      })
      .then(() => {
        // call a function to update the state (and therefore the table) on the new values.
        data.stock = Number(stock);
        modalUpdate(data);
        onHide();
      });
  };

  if (show && data) {
    return (
      <Modal show={show} onHide={onSave}>
        <Modal.Header closeButton>
          <Modal.Title>{data.name}</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <div className="input-group">
            <div className="input-group-prepend">
              <span className="input-group-text">Current Stock</span>
            </div>
            <input
              className="form-control"
              type="number"
              value={stock}
              onChange={(e) => {
                setStock(e.target.value);
              }}
            />
          </div>
        </Modal.Body>
        <Modal.Footer>
          <Button variant="primary" onClick={onSave}>
            Save & Close
          </Button>
          <Button variant="secondary" onClick={onHide}>
            Close
          </Button>
        </Modal.Footer>
      </Modal>
    );
  } else {
    return null;
  }
};
export default ManualEditModal;

thank you in advance for the help, i hope this can be done... on this problem for 2 weeks!!

Update: As of now, i tried (with no success):

  1. removing the useMemo hook from the data constant to the table, making it rerender more often but it does not seem to cut it.
  2. Passing the data from props as a new array and spreading the old one in to it.
0

There are 0 best solutions below