React Table 7 - 在 Table 加载时扩展行

React Table 7 - Expand Rows on Table Load

我正在尝试在 table 加载时自动扩展反应 table 7。如果我硬编码 tables expanded initialState 它可以工作,但我需要能够以编程方式执行它,因为加载的行数会根据其他数据选择因素而变化。

我已经设置了我的 table 以便它接受 2 个道具,expandedRows 是一个布尔值,expandedRowObj 这是一个包含每一行的索引的对象和要扩展的真值。

我正在使用 useEffect 循环数据并创建一个新对象,该对象将数据索引作为键并将 true 设置为 属性。然后我将这个对象数组作为道具传递给 tables initialState.

我可以看到使用 devTools table 上的 intitalState 被设置为:

initialState: {
   expanded: [{0: true}, {1: true}, {2: true},{3: true}]
}

但是,行没有展开。

如果我不使用 useEffect 函数来设置 expandedRows 状态,而只是硬编码一个名为 expandedRows 的变量,则 table 会按预期展开。我猜 table 渲染和设置初始状态之间存在断开连接,但我不确定。

这里是演示问题的沙箱:https://codesandbox.io/s/dazzling-tdd-x4890?file=/src/App.js

对于那些不想点击链接的人,这里是所有相关代码:

TABLE


import {
  useTable,
  useSortBy,
  useGlobalFilter,
  useFilters,
  useResizeColumns,
  useFlexLayout,
  useExpanded,
  usePagination
} from "react-table";
import {
  Table,
  InputGroup,
  FormControl,
  Row,
  Col,
  Button
} from "react-bootstrap";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
  faArrowDown,
  faArrowUp,
  faAngleDoubleLeft,
  faAngleDoubleRight,
  faAngleLeft,
  faAngleRight
} from "@fortawesome/free-solid-svg-icons";

import GlobalFilter from "./GlobalFilter";
import ColumnFilter from "./ColumnFilter";

import "./Table.css";
import "bootstrap/dist/css/bootstrap.min.css";

const MyTable = ({
  columns: userColumns,
  data,
  renderRowSubComponent,
  rowOnClick,
  rowClickHandler,
  headerColor,
  showPagination,
  showGlobalFilter,
  expandRows,
  expandedRowObj
}) => {
  const filterTypes = React.useMemo(
    () => ({
      includes: (rows, id, filterValue) => {
        return rows.filter((row) => {
          const rowValue = row.values[id];
          return rowValue !== undefined
            ? String(rowValue)
                .toLowerCase()
                .includes(String(filterValue).toLowerCase())
            : true;
        });
      },

      startsWith: (rows, id, filterValue) => {
        return rows.filter((row) => {
          const rowValue = row.values[id];
          return rowValue !== undefined
            ? String(rowValue)
                .toLowerCase()
                .startsWith(String(filterValue).toLowerCase())
            : true;
        });
      }
    }),
    []
  );

  const sortTypes = React.useMemo(
    () => ({
      dateSort: (a, b) => {
        a = new Date(a).getTime();
        b = new Date(b).getTime();
        return b > a ? 1 : -1;
      }
    }),
    []
  );

  const defaultColumn = React.useMemo(
    () => ({
      Filter: ColumnFilter,
      disableFilters: true,
      minWidth: 30,
      width: 150,
      maxWidth: 500
    }),
    []
  );

  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    prepareRow,
    page,
    canPreviousPage,
    canNextPage,
    pageOptions,
    pageCount,
    gotoPage,
    nextPage,
    previousPage,
    setPageSize,
    setGlobalFilter,
    state: { globalFilter, pageIndex, pageSize }
  } = useTable(
    {
      columns: userColumns,
      data,
      initialState: {
        expanded:
          expandRows && expandedRowObj.hasOwnProperty(0) ? expandedRowObj : {}
      },
      defaultColumn,
      filterTypes,
      sortTypes
    },
    useGlobalFilter,
    useFilters,
    useSortBy,
    useResizeColumns,
    useExpanded,
    usePagination,
    useFlexLayout
  );

  return (
    <React.Fragment>
      <Row className="float-right">
        <Col>
          {showGlobalFilter ? (
            <GlobalFilter filter={globalFilter} setFilter={setGlobalFilter} />
          ) : (
            ""
          )}
        </Col>
      </Row>
      <Row>
        <Col>
          <Table
            striped
            bordered
            hover
            size="sm"
            responsive
            {...getTableProps()}
          >
            <thead>
              {headerGroups.map((headerGroup, i) => (
                <React.Fragment key={headerGroup.headers.length + "_hfrag"}>
                  <tr {...headerGroup.getHeaderGroupProps()}>
                    {headerGroup.headers.map((column) => (
                      <th
                        key={column.id}
                        className={`p-2 table-header ${
                          headerColor ? "primary-" + headerColor : "primary-deq"
                        }`}
                        {...column.getHeaderProps()}
                      >
                        <span {...column.getSortByToggleProps()}>
                          {column.render("Header")}
                          {column.isSorted ? (
                            column.isSortedDesc ? (
                              <FontAwesomeIcon
                                className="ms-3"
                                icon={faArrowDown}
                              />
                            ) : (
                              <FontAwesomeIcon
                                className="ms-3"
                                icon={faArrowUp}
                              />
                            )
                          ) : (
                            ""
                          )}
                        </span>
                        <div
                          {...column.getResizerProps()}
                          className="resizer"
                        />
                        {column.canResize && (
                          <div
                            {...column.getResizerProps()}
                            className={`resizer ${
                              column.isResizing ? "isResizing" : ""
                            }`}
                          />
                        )}
                        <div>
                          {column.canFilter ? column.render("Filter") : null}
                        </div>
                      </th>
                    ))}
                  </tr>
                </React.Fragment>
              ))}
            </thead>
            <tbody {...getTableBodyProps()}>
              {page.map((row, i) => {
                prepareRow(row);
                return (
                  <React.Fragment key={i + "_frag"}>
                    <tr
                      {...row.getRowProps()}
                      onClick={
                        rowOnClick
                          ? () => rowClickHandler(row.original)
                          : () => ""
                      }
                    >
                      {row.cells.map((cell) => {
                        return (
                          <td {...cell.getCellProps()}>
                            {cell.render("Cell")}
                          </td>
                        );
                      })}
                    </tr>
                    {row.isExpanded ? (
                      <tr>
                        <td>
                          <span className="subTable">
                            {renderRowSubComponent({ row })}
                          </span>
                        </td>
                      </tr>
                    ) : null}
                  </React.Fragment>
                );
              })}
            </tbody>
          </Table>
          {showPagination ? (
            <Row className="mt-2 text-center">
              <Col>
                <Button
                  className="me-2"
                  size="sm"
                  variant="secondary"
                  onClick={() => gotoPage(0)}
                  disabled={!canPreviousPage}
                >
                  <FontAwesomeIcon icon={faAngleDoubleLeft} />
                </Button>
                <Button
                  className="me-2"
                  size="sm"
                  variant="secondary"
                  onClick={() => previousPage()}
                  disabled={!canPreviousPage}
                >
                  <FontAwesomeIcon icon={faAngleLeft} />
                </Button>
              </Col>
              <Col>
                <span>
                  Page{" "}
                  <strong>
                    {pageIndex + 1} of {pageOptions.length}
                  </strong>
                </span>
                <span>
                  | Go to page:{" "}
                  <InputGroup
                    size="sm"
                    style={{ width: "20%", display: "inline-flex" }}
                  >
                    <FormControl
                      type="number"
                      defaultValue={pageIndex + 1}
                      onChange={(e) => {
                        const page = e.target.value
                          ? Number(e.target.value) - 1
                          : 0;
                        gotoPage(page);
                      }}
                    />
                  </InputGroup>
                </span>
                <InputGroup
                  size="sm"
                  style={{ width: "30%", display: "inline-flex" }}
                >
                  <FormControl
                    className="mt-4"
                    size="sm"
                    as="select"
                    value={pageSize}
                    onChange={(e) => {
                      setPageSize(Number(e.target.value));
                    }}
                  >
                    {[5, 10, 20, 30, 40, 50].map((pageSize) => (
                      <option key={pageSize} value={pageSize}>
                        Show {pageSize}
                      </option>
                    ))}
                  </FormControl>
                </InputGroup>
              </Col>
              <Col>
                <Button
                  className="me-2"
                  size="sm"
                  variant="secondary"
                  onClick={() => nextPage()}
                  disabled={!canNextPage}
                >
                  <FontAwesomeIcon icon={faAngleRight} />
                </Button>
                <Button
                  className="me-2"
                  size="sm"
                  variant="secondary"
                  onClick={() => gotoPage(pageCount - 1)}
                  disabled={!canNextPage}
                >
                  <FontAwesomeIcon icon={faAngleDoubleRight} />
                </Button>
              </Col>
            </Row>
          ) : (
            ""
          )}
        </Col>
      </Row>
    </React.Fragment>
  );
};

MyTable.defaultProps = {
  rowOnClick: false,
  showPagination: false,
  expandRows: false,
  expandedRowObj: {}
};

MyTable.propTypes = {
  /** Specified if pagination should show or not */
  showPagination: PropTypes.bool.isRequired,

  /** Specifies if there should be a row onClick action*/
  rowOnClick: PropTypes.bool.isRequired,

  /** OPTIONAL: The onClick Action to be taken */
  rowClickHandler: PropTypes.func,

  /** header color background. There are six possible choices. Refer to ReadMe file for specifics */
  headerColor: PropTypes.string
};


使用 TABLE 组件


const GroupedSamplingStationTable = (props) => {
  const [expandedRows, setExpandedRows] = useState();
  //const expandedRows = [{ 0: true }, { 1: true }, { 2: true }, { 3: true }]; //This works

  const columns = [
    {
      Header: () => null,
      id: "expander",
      width: 30,
      Cell: ({ row }) => (
        <span {...row.getToggleRowExpandedProps()}>
          {row.isExpanded ? (
            <FontAwesomeIcon className="font-icon" icon={faCaretDown} />
          ) : (
            <FontAwesomeIcon className="font-icon" icon={faCaretRight} />
          )}
        </span>
      )
    },
    {
      Header: "Sample Group ID",
      accessor: "groupId",
      width: 75
    },
    {
      Header: "Sample Group",
      accessor: "groupName",
      width: 200
    }
  ];

  const details = React.useMemo(
    () => [
      {
        Header: "Source ID",
        accessor: "sourceId",
        width: 50
      },
      {
        Header: "Source Name",
        accessor: "sourceName",
        width: 125
      },
      {
        Header: "Sample Group Details",
        accessor: "groupDetails",
        width: 100
      },
      {
        Header: "System",
        accessor: (d) => {
          return d.systemNumber + " " + d.systemName;
        },
        width: 200
      }
    ],
    []
  );

  const subTable = React.useCallback(
    ({ row }) =>
      row.original.groupDetails.length > 0 ? (
        <MyTable
          columns={details}
          data={row.original.groupDetails}
          headerColor="grey"
        />
      ) : (
        "No Data"
      ),
    [details]
  );

  useEffect(() => {
    if (data) {
      let array = [];
      if (data.data.getGroupedSamplingStationBySystemId.length > 0) {
        data.data.getGroupedSamplingStationBySystemId.forEach((elem, index) => {
          let obj = {};
          obj[index] = true;
          array.push(obj);
        });
      } else {
        let obj = {};
        obj[0] = false;
      }
      setExpandedRows(array);
    }
  }, []);

  return (
    <>
      {data.data.getGroupedSamplingStationBySystemId.length > 0 ? (
        <MyTable
          data={data.data.getGroupedSamplingStationBySystemId}
          columns={columns}
          renderRowSubComponent={subTable}
          expandRows={true}
          expandedRowObj={expandedRows}
        />
      ) : (
        <span>
          <em>No data was found for grouped sampling stations.</em>
        </span>
      )}
    </>
  );
};

数据示例

data = {
  data: {
    getGroupedSamplingStationBySystemId: [
      {
        systemId: 1289,
        groupId: "8053",
        groupName: "S28-UTAH18026UTAH18103",
        groupDetails: [
          {
            sourceId: "WS005",
            sourceName: "MT OLYMPUS SPRING ABND",
            groupDetails: " ",
            systemNumber: "UTAH18026",
            systemName: "SALT LAKE CITY WATER SYSTEM"
          },
          {
            sourceId: "WS001",
            sourceName: "MT OLYMPUS SPRING",
            groupDetails: " ",
            systemNumber: "UTAH18103",
            systemName: "MOUNT OLYMPUS WATERS"
          }
        ]
      },
      {
        systemId: 1289,
        groupId: "8085",
        groupName: "S29-UTAH18026UTAH18050",
        groupDetails: [
          {
            sourceId: "WS007",
            sourceName: "LOWER BOUNDARY SPRING TSFR",
            groupDetails: " ",
            systemNumber: "UTAH18026",
            systemName: "SALT LAKE CITY WATER SYSTEM"
          },
          {
            sourceId: "WS001",
            sourceName: "LOWER BOUNDARY SPRING",
            groupDetails: " ",
            systemNumber: "UTAH18050",
            systemName: "BOUNDARY SPRING WATER CO"
          }
        ]
      },
      {
        systemId: 1289,
        groupId: "8193",
        groupName: "S30-UTAH18026UTAH18028",
        groupDetails: [
          {
            sourceId: "WS039",
            sourceName: "RICHARDS DITCH WELL [DISCONNECTED]",
            groupDetails: "IGNORE THIS ONE",
            systemNumber: "UTAH18026",
            systemName: "SALT LAKE CITY WATER SYSTEM"
          },
          {
            sourceId: "WS027",
            sourceName: "RICHARDS DITCH WELL (SOLD/TRANSFERRED)",
            groupDetails: " ",
            systemNumber: "UTAH18028",
            systemName: "SANDY CITY WATER SYSTEM"
          }
        ]
      },
      {
        systemId: 1289,
        groupId: "7956",
        groupName: "S63-UTAH18026UTAH18028",
        groupDetails: [
          {
            sourceId: "WS031",
            sourceName: "7901 S HIGHLAND WELL TSFR",
            groupDetails: " ",
            systemNumber: "UTAH18026",
            systemName: "SALT LAKE CITY WATER SYSTEM"
          },
          {
            sourceId: "WS026",
            sourceName: "LITTLE COTTONWOOD WELL",
            groupDetails: " ",
            systemNumber: "UTAH18028",
            systemName: "SANDY CITY WATER SYSTEM"
          }
        ]
      }
    ]
  }
};


使用记忆数组,而不是使用 useEffect 改变的状态数组,似乎工作得很好(sandbox):

  const expandedRows = React.useMemo(() => {
    if (data?.data) {
      let arr = [{0: false}];
      let d = data.data;
      if (d.getGroupedSamplingStationBySystemId.length > 0) {
        arr = d.getGroupedSamplingStationBySystemId.map((sid, ind) => {
          return { [ind]: true };
        });
      }
      return arr;
    }
  }, []);