export function Table()

in superset-frontend/src/components/Table/index.tsx [240:426]


export function Table<RecordType extends object>(
  props: TableProps<RecordType>,
) {
  const {
    data,
    bordered,
    columns,
    selectedRows = defaultRowSelection,
    handleRowSelection,
    size = TableSize.Small,
    selectionType = SelectionType.Disabled,
    sticky = true,
    loading = false,
    resizable = false,
    reorderable = false,
    usePagination = true,
    defaultPageSize = 15,
    pageSizeOptions = ['5', '15', '25', '50', '100'],
    hideData = false,
    locale,
    height,
    virtualize = false,
    onChange = noop,
    recordCount,
    onRow,
    allowHTML = false,
    childrenColumnName,
  } = props;

  const wrapperRef = useRef<HTMLDivElement | null>(null);
  const [derivedColumns, setDerivedColumns] = useState(columns);
  const [pageSize, setPageSize] = useState(defaultPageSize);
  const [mergedLocale, setMergedLocale] = useState<
    Required<AntTableProps<RecordType>>['locale']
  >({ ...defaultLocale });
  const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>(selectedRows);
  const interactiveTableUtils = useRef<InteractiveTableUtils | null>(null);

  const onSelectChange = (newSelectedRowKeys: Key[]) => {
    setSelectedRowKeys(newSelectedRowKeys);
    handleRowSelection?.(newSelectedRowKeys);
  };

  const selectionTypeValue = selectionMap[selectionType];
  const rowSelection = {
    type: selectionMap[selectionType] as RowSelectionType,
    selectedRowKeys,
    onChange: onSelectChange,
  };

  // Log use of experimental features
  useEffect(() => {
    if (reorderable === true) {
      logging.warn(
        'EXPERIMENTAL FEATURE ENABLED: The "reorderable" prop of Table is experimental and NOT recommended for use in production deployments.',
      );
    }
    if (resizable === true) {
      logging.warn(
        'EXPERIMENTAL FEATURE ENABLED: The "resizable" prop of Table is experimental and NOT recommended for use in production deployments.',
      );
    }
  }, [reorderable, resizable]);

  useEffect(() => {
    let updatedLocale;
    if (locale) {
      // This spread allows for locale to only contain a subset of locale overrides on props
      updatedLocale = { ...defaultLocale, ...locale };
    } else {
      updatedLocale = { ...defaultLocale };
    }
    setMergedLocale(updatedLocale);
  }, [locale]);

  useEffect(() => setDerivedColumns(columns), [columns]);

  useEffect(() => {
    if (interactiveTableUtils.current) {
      interactiveTableUtils.current?.clearListeners();
    }
    const table = wrapperRef.current?.getElementsByTagName('table')[0];
    if (table) {
      interactiveTableUtils.current = new InteractiveTableUtils(
        table,
        derivedColumns,
        setDerivedColumns,
      );
      if (reorderable) {
        interactiveTableUtils?.current?.initializeDragDropColumns(
          reorderable,
          table,
        );
      }
      if (resizable) {
        interactiveTableUtils?.current?.initializeResizableColumns(
          resizable,
          table,
        );
      }
    }
    return () => {
      interactiveTableUtils?.current?.clearListeners?.();
    };
    /**
     * We DO NOT want this effect to trigger when derivedColumns changes as it will break functionality
     * The exclusion from the effect dependencies is intentional and should not be modified
     */
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [wrapperRef, reorderable, resizable, virtualize, interactiveTableUtils]);

  const theme = useTheme();

  const paginationSettings: PaginationProps | false = usePagination
    ? {
        hideOnSinglePage: true,
        pageSize,
        pageSizeOptions,
        onShowSizeChange: (page: number, size: number) => setPageSize(size),
      }
    : false;

  /**
   * When recordCount is provided it lets the user of Table control total number of pages
   * independent from data.length.  This allows the parent component do things like server side paging
   * where the user can be shown the total mount of data they can page through, but the component can provide
   * data one page at a time, and respond to the onPageChange event to fetch and set new data
   */
  if (paginationSettings && recordCount) {
    paginationSettings.total = recordCount;
  }

  let bodyHeight = height;
  if (bodyHeight) {
    bodyHeight -= HEADER_HEIGHT;
    const hasPagination =
      usePagination && recordCount && recordCount > pageSize;
    if (hasPagination) {
      bodyHeight -= PAGINATION_HEIGHT;
    }
  }

  const sharedProps = {
    loading: { spinning: loading ?? false, indicator: <Loading /> },
    hasData: hideData ? false : data,
    columns: derivedColumns,
    dataSource: hideData ? undefined : data,
    size,
    pagination: paginationSettings,
    locale: mergedLocale,
    showSorterTooltip: false,
    onChange,
    onRow,
    theme,
    height: bodyHeight,
    bordered,
    expandable: {
      childrenColumnName,
    },
  };

  return (
    <div ref={wrapperRef}>
      {!virtualize && (
        <StyledTable
          {...sharedProps}
          rowSelection={selectionTypeValue !== null ? rowSelection : undefined}
          sticky={sticky}
        />
      )}
      {virtualize && (
        <StyledVirtualTable
          {...sharedProps}
          scroll={{
            y: 300,
            x: '100vw',
            // To avoid jest failure by scrollTo
            ...(process.env.WEBPACK_MODE === 'test' && {
              scrollToFirstRowOnChange: false,
            }),
          }}
          allowHTML={allowHTML}
        />
      )}
    </div>
  );
}