如何在 React / TypeScript 中设置 material-table 动态列

How to set material-table dynamic columns in react / typescript

我有一个 table 必须显示任意数量的列,这些列是根据接收到的数据动态生成的。

我已经创建了一个列界面,它将包含我需要的每一列的所有属性,例如该列是否可以最小化(变窄),它是否是我必须为其呈现图标的列结构或可能为组行呈现特定数据等

在 material-table 中,我试图映射这些列,但总是出现错误: 错误:this.tableContainerDiv.current 为空

即使对象列表与我对列进行硬编码时完全相同,也会发生这种情况。

我的(缩减)代码:

interface IColumnObject {
  field: string;
  title: string;
  sorting: boolean;
  minimizing: boolean;
  minimized: boolean;
  hidden: boolean;
  width: string;
  grouprender: boolean;
  iconrender: boolean;
}

interface State {
  detailData: ITableData[];
  filteredData: ITableData[];
  headers: IHeaderObject[];
  columns: IColumnObject[];
  filterList: IFilterList;
  anchorEl: Element;
  csvHeader: ICsvHeader[];
  csvData: ICsvData[];
}

class DetailAllRoute extends React.Component<Props, State> {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  private csvLink: React.RefObject<any> = React.createRef();
...
  public generateTableDetailData = () => {
    const tableData: ITableData[] = [];
    let createHeader = true;
    let createColumn = true;
    const headers: IHeaderObject[] = [];
    const columns: IColumnObject[] = [];
...
    // Add the Application, Filename and Location column objects to the columns list
    const applicationColumn: IColumnObject = {
      field: 'application',
      title: this.i18n.translateToString('Column_Application'),
      sorting: true,
      minimizing: false,
      minimized: false,
      hidden: false,
      width: '100px',
      grouprender: false,
      iconrender: false,
    };

    columns.push(applicationColumn);

    const filenameColumn: IColumnObject = {
      field: 'filename',
      title: this.i18n.translateToString('Column_Filename'),
      sorting: true,
      minimizing: false,
      minimized: false,
      hidden: false,
      width: '270px',
      grouprender: true,
      iconrender: false,
    };

    columns.push(filenameColumn);

    const locationColumn: IColumnObject = {
      field: 'location',
      title: this.i18n.translateToString('Column_Location'),
      sorting: true,
      minimizing: true,
      minimized: false,
      hidden: false,
      width: '350px',
      grouprender: false,
      iconrender: false,
    };

    columns.push(locationColumn);
  ...
  }

  render() {
    const { classes } = this.props;
    const {
      compData,
      filteredData,
      columns,
      filterList,
      anchorEl,
    } = this.state;

    return (
      <React.Fragment>
        <Paper className={classes.muiListRoot} style={{ backgroundColor: '#fff' }}>
          <MaterialTable
            title={
              <span style={{ fontSize: '2.0em', fontWeight: 'bold', color: '#19768B' }}>
                {`${this.i18n.translateToString('Table_DetailData')} Test`}
              </span>
            }
            actions={[
              {
                icon: FilterList,
                tooltip: 'Filter',
                position: 'toolbar',
                onClick: event => {
                  this.handlePopoverClick(event);
                },
              },
            ]}
            components={{
              // eslint-disable-next-line react/display-name
              Header: headerprops => (
                <React.Fragment>
                  {this.getTableHeader()}
                  <MTableHeader {...headerprops} />
                </React.Fragment>
              ),
            }}
            columns={columns.map(c => {
              return {
                title: c.title,
                field: c.field,
                sorting: c.sorting,
                width: c.width,
                hidden: c.hidden,
              } as Column<any>;
            })}
            data={filteredData}
            parentChildData={(row, rows) => rows.find(a => a.application === row.parent)}
            options={{
              toolbar: true,
              sorting: true,
              exportButton: { csv: true },
              exportCsv: () => {
                this.customExportCSV();
              },
              headerStyle: {
                backgroundColor: '#19768B',
                color: 'white',
                borderBottom: '1px solid black',
              },
              // eslint-disable-next-line @typescript-eslint/no-explicit-any
              rowStyle: (_data: any, index: number) => {
                return index % 2 ? { backgroundColor: '#ecf2f9' } : {};
              },
            }}
            localization={{
              pagination: {
                labelDisplayedRows: this.i18n.translateToString('String_RowFromToCount'),
                labelRowsSelect: this.i18n.translateToString('String_Rows'),
                labelRowsPerPage: this.i18n.translateToString('String_RowsPerPage'),
                firstAriaLabel: this.i18n.translateToString('String_FirstPage'),
                firstTooltip: this.i18n.translateToString('String_FirstPage'),
                previousAriaLabel: this.i18n.translateToString('String_PrevPage'),
                previousTooltip: this.i18n.translateToString('String_PrevPage'),
                nextAriaLabel: this.i18n.translateToString('String_NextPage'),
                nextTooltip: this.i18n.translateToString('String_NextPage'),
                lastAriaLabel: this.i18n.translateToString('String_LastPage'),
                lastTooltip: this.i18n.translateToString('String_LastPage'),
              },
              toolbar: {
                nRowsSelected: this.i18n.translateToString('String_RowsSelected'),
                searchTooltip: this.i18n.translateToString('String_Search'),
                searchPlaceholder: this.i18n.translateToString('String_Search'),
                exportTitle: this.i18n.translateToString('String_Export'),
                exportCSVName: this.i18n.translateToString('String_ExportAs'),
              },
              body: {
                emptyDataSourceMessage: this.i18n.translateToString('String_NoData'),
              },
            }}
          />
        </Paper>
        <div>
          <CSVLink
            data={csvData}
            headers={csvHeader}
            filename="CSV_File.csv"
            ref={this.csvLink}
            target="_blank"
          />
        </div>
      </React.Fragment>
    );
  }
}

知道我在映射中做错了什么吗?还是 material-table 不喜欢使用 map 生成列数组?

当我对列进行硬编码时:

    ...
            columns={[
              {
                title: this.i18n.translateToString('Column_Application'),
                field: 'application',
                sorting: true,
                width: '100px',
                hidden: true,
              },
              {
                title: this.i18n.translateToString('Column_Filename'),
                field: 'filename',
                sorting: true,
                width: '270px',
                // eslint-disable-next-line react/display-name
                render: rowData =>
                  !!!rowData.filename ? (
                    <span>
                      <b>{this.i18n.translateToString('Column_Application')}: </b>
                      {rowData.application}
                    </span>
                  ) : (
                    rowData.filename
                  ),
              },
              {
                title: this.i18n.translateToString('Column_Location'),
                field: 'location',
                sorting: true,
              },
            ]}
    ...

一切正常。但是我需要映射列,因为列是根据接收到的数据动态生成的。

与大多数情况一样,问题是 programmer-error。我花了很长时间才弄明白,但最终在一位同事的帮助下顺利完成。在他的指导下,我终于大致了解了问题的根源。

最后发现系统报错,找不到列名。控制台中显示的(高得多的)错误非常具有误导性,因为它指出使用了无效(空)引用,事实证明并非如此。由于某些未知原因,我无意中在添加每行数据的最后一步中没有使用生成的唯一列名,而是使用了 hard-coded 列名。容易被忽视,但咬得很紧......