chore: refactor FilterableTable to functional component (#21136)

This commit is contained in:
EugeneTorap 2022-08-22 09:19:36 +03:00 committed by GitHub
parent 25c2b7f761
commit 7c0963f6ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -17,7 +17,7 @@
* under the License. * under the License.
*/ */
import JSONbig from 'json-bigint'; import JSONbig from 'json-bigint';
import React, { PureComponent } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import JSONTree from 'react-json-tree'; import JSONTree from 'react-json-tree';
import { import {
AutoSizer, AutoSizer,
@ -33,8 +33,7 @@ import {
getMultipleTextDimensions, getMultipleTextDimensions,
t, t,
styled, styled,
SupersetTheme, useTheme,
withTheme,
} from '@superset-ui/core'; } from '@superset-ui/core';
import Button from '../Button'; import Button from '../Button';
import CopyToClipboard from '../CopyToClipboard'; import CopyToClipboard from '../CopyToClipboard';
@ -188,98 +187,65 @@ export interface FilterableTableProps {
orderedColumnKeys: string[]; orderedColumnKeys: string[];
data: Record<string, unknown>[]; data: Record<string, unknown>[];
height: number; height: number;
filterText: string; filterText?: string;
headerHeight: number; headerHeight?: number;
overscanColumnCount: number; overscanColumnCount?: number;
overscanRowCount: number; overscanRowCount?: number;
rowHeight: number; rowHeight?: number;
striped: boolean; striped?: boolean;
expandedColumns: string[]; expandedColumns?: string[];
theme: SupersetTheme;
} }
interface FilterableTableState { const FilterableTable = ({
sortBy?: string; orderedColumnKeys,
sortDirection?: SortDirectionType; data,
fitted: boolean; height,
displayedList: Datum[]; filterText = '',
} headerHeight = 32,
overscanColumnCount = 10,
overscanRowCount = 10,
rowHeight = 32,
striped = true,
expandedColumns = [],
}: FilterableTableProps) => {
const formatTableData = (data: Record<string, unknown>[]): Datum[] =>
data.map(row => {
const newRow = {};
Object.entries(row).forEach(([key, val]) => {
if (['string', 'number'].indexOf(typeof val) >= 0) {
newRow[key] = val;
} else {
newRow[key] = val === null ? null : JSONbig.stringify(val);
}
});
return newRow;
});
class FilterableTable extends PureComponent< const [sortByState, setSortByState] = useState<string | undefined>(undefined);
FilterableTableProps, const [sortDirectionState, setSortDirectionState] = useState<
FilterableTableState SortDirectionType | undefined
> { >(undefined);
static defaultProps = { const [fitted, setFitted] = useState(false);
filterText: '', const [list] = useState<Datum[]>(() => formatTableData(data));
headerHeight: 32, const [displayedList, setDisplayedList] = useState<Datum[]>(list);
overscanColumnCount: 10,
overscanRowCount: 10,
rowHeight: 32,
striped: true,
expandedColumns: [],
};
list: Datum[]; // columns that have complex type and were expanded into sub columns
const [complexColumns] = useState<Record<string, boolean>>(
complexColumns: Record<string, boolean>; orderedColumnKeys.reduce(
widthsForColumnsByKey: Record<string, number>;
totalTableWidth: number;
totalTableHeight: number;
container: React.RefObject<HTMLDivElement>;
jsonTreeTheme: Record<string, string>;
constructor(props: FilterableTableProps) {
super(props);
this.list = this.formatTableData(props.data);
this.addJsonModal = this.addJsonModal.bind(this);
this.getCellContent = this.getCellContent.bind(this);
this.renderGridCell = this.renderGridCell.bind(this);
this.renderGridCellHeader = this.renderGridCellHeader.bind(this);
this.renderGrid = this.renderGrid.bind(this);
this.renderTableCell = this.renderTableCell.bind(this);
this.renderTableHeader = this.renderTableHeader.bind(this);
this.sortResults = this.sortResults.bind(this);
this.renderTable = this.renderTable.bind(this);
this.rowClassName = this.rowClassName.bind(this);
this.sort = this.sort.bind(this);
this.getJsonTreeTheme = this.getJsonTreeTheme.bind(this);
// columns that have complex type and were expanded into sub columns
this.complexColumns = props.orderedColumnKeys.reduce(
(obj, key) => ({ (obj, key) => ({
...obj, ...obj,
[key]: props.expandedColumns.some(name => name.startsWith(`${key}.`)), [key]: expandedColumns.some(name => name.startsWith(`${key}.`)),
}), }),
{}, {},
); ),
);
this.widthsForColumnsByKey = this.getWidthsForColumns(); const theme = useTheme();
this.totalTableWidth = props.orderedColumnKeys const [jsonTreeTheme, setJsonTreeTheme] = useState<Record<string, string>>();
.map(key => this.widthsForColumnsByKey[key])
.reduce((curr, next) => curr + next);
this.totalTableHeight = props.height;
this.state = { const getJsonTreeTheme = () => {
fitted: false, if (!jsonTreeTheme) {
displayedList: [...this.list], setJsonTreeTheme({
};
this.container = React.createRef();
}
componentDidMount() {
this.fitTableToWidthIfNeeded();
}
getJsonTreeTheme() {
if (!this.jsonTreeTheme) {
const { theme } = this.props;
this.jsonTreeTheme = {
base00: theme.colors.grayscale.dark2, base00: theme.colors.grayscale.dark2,
base01: theme.colors.grayscale.dark1, base01: theme.colors.grayscale.dark1,
base02: theme.colors.grayscale.base, base02: theme.colors.grayscale.base,
@ -296,22 +262,20 @@ class FilterableTable extends PureComponent<
base0D: theme.colors.primary.base, base0D: theme.colors.primary.base,
base0E: theme.colors.primary.dark1, base0E: theme.colors.primary.dark1,
base0F: theme.colors.error.dark1, base0F: theme.colors.error.dark1,
}; });
} }
return this.jsonTreeTheme; return jsonTreeTheme;
} };
getDatum(list: Datum[], index: number) { const getDatum = (list: Datum[], index: number) => list[index % list.length];
return list[index % list.length];
}
getWidthsForColumns() { const getWidthsForColumns = () => {
const PADDING = 50; // accounts for cell padding and width of sorting icon const PADDING = 50; // accounts for cell padding and width of sorting icon
const widthsByColumnKey = {}; const widthsByColumnKey = {};
const cellContent = ([] as string[]).concat( const cellContent = ([] as string[]).concat(
...this.props.orderedColumnKeys.map(key => { ...orderedColumnKeys.map(key => {
const cellContentList = this.list.map((data: Datum) => const cellContentList = list.map((data: Datum) =>
this.getCellContent({ cellData: data[key], columnKey: key }), getCellContent({ cellData: data[key], columnKey: key }),
); );
cellContentList.push(key); cellContentList.push(key);
return cellContentList; return cellContentList;
@ -323,30 +287,26 @@ class FilterableTable extends PureComponent<
texts: cellContent, texts: cellContent,
}).map(dimension => dimension.width); }).map(dimension => dimension.width);
this.props.orderedColumnKeys.forEach((key, index) => { orderedColumnKeys.forEach((key, index) => {
// we can't use Math.max(...colWidths.slice(...)) here since the number // we can't use Math.max(...colWidths.slice(...)) here since the number
// of elements might be bigger than the number of allowed arguments in a // of elements might be bigger than the number of allowed arguments in a
// Javascript function // JavaScript function
const value = (widthsByColumnKey[key] = widthsByColumnKey[key] =
colWidths colWidths
.slice( .slice(index * (list.length + 1), (index + 1) * (list.length + 1))
index * (this.list.length + 1), .reduce((a, b) => Math.max(a, b)) + PADDING;
(index + 1) * (this.list.length + 1),
)
.reduce((a, b) => Math.max(a, b)) + PADDING);
widthsByColumnKey[key] = value;
}); });
return widthsByColumnKey; return widthsByColumnKey;
} };
getCellContent({ const getCellContent = ({
cellData, cellData,
columnKey, columnKey,
}: { }: {
cellData: CellDataType; cellData: CellDataType;
columnKey: string; columnKey: string;
}) { }) => {
if (cellData === null) { if (cellData === null) {
return 'NULL'; return 'NULL';
} }
@ -360,24 +320,35 @@ class FilterableTable extends PureComponent<
} else { } else {
truncated = ''; truncated = '';
} }
return this.complexColumns[columnKey] ? truncated : content; return complexColumns[columnKey] ? truncated : content;
} };
formatTableData(data: Record<string, unknown>[]): Datum[] { const [widthsForColumnsByKey] = useState<Record<string, number>>(() =>
return data.map(row => { getWidthsForColumns(),
const newRow = {}; );
Object.entries(row).forEach(([key, val]) => {
if (['string', 'number'].indexOf(typeof val) >= 0) {
newRow[key] = val;
} else {
newRow[key] = val === null ? null : JSONbig.stringify(val);
}
});
return newRow;
});
}
hasMatch(text: string, row: Datum) { const totalTableWidth = useRef(
orderedColumnKeys
.map(key => widthsForColumnsByKey[key])
.reduce((curr, next) => curr + next),
);
const totalTableHeight = useRef(height);
const container = useRef<HTMLDivElement>(null);
const fitTableToWidthIfNeeded = () => {
const containerWidth = container.current?.clientWidth ?? 0;
if (totalTableWidth.current < containerWidth) {
// fit table width if content doesn't fill the width of the container
totalTableWidth.current = containerWidth;
}
setFitted(true);
};
useEffect(() => {
fitTableToWidthIfNeeded();
}, []);
const hasMatch = (text: string, row: Datum) => {
const values: string[] = []; const values: string[] = [];
Object.keys(row).forEach(key => { Object.keys(row).forEach(key => {
if (row.hasOwnProperty(key)) { if (row.hasOwnProperty(key)) {
@ -394,82 +365,60 @@ class FilterableTable extends PureComponent<
}); });
const lowerCaseText = text.toLowerCase(); const lowerCaseText = text.toLowerCase();
return values.some(v => v.includes(lowerCaseText)); return values.some(v => v.includes(lowerCaseText));
} };
rowClassName({ index }: { index: number }) { const rowClassName = ({ index }: { index: number }) => {
let className = ''; let className = '';
if (this.props.striped) { if (striped) {
className = index % 2 === 0 ? 'even-row' : 'odd-row'; className = index % 2 === 0 ? 'even-row' : 'odd-row';
} }
return className; return className;
} };
sort({ const sort = ({
sortBy, sortBy,
sortDirection, sortDirection,
}: { }: {
sortBy: string; sortBy: string;
sortDirection: SortDirectionType; sortDirection: SortDirectionType;
}) { }) => {
let updatedState: FilterableTableState;
const shouldClearSort = const shouldClearSort =
this.state.sortDirection === SortDirection.DESC && sortDirectionState === SortDirection.DESC && sortByState === sortBy;
this.state.sortBy === sortBy;
if (shouldClearSort) { if (shouldClearSort) {
updatedState = { setSortByState(undefined);
...this.state, setSortDirectionState(undefined);
sortBy: undefined, setDisplayedList([...list]);
sortDirection: undefined,
displayedList: [...this.list],
};
} else { } else {
updatedState = { setSortByState(sortBy);
...this.state, setSortDirectionState(sortDirection);
sortBy, setDisplayedList(
sortDirection, [...list].sort(
displayedList: [...this.list].sort( sortResults(sortBy, sortDirection === SortDirection.DESC),
this.sortResults(sortBy, sortDirection === SortDirection.DESC),
), ),
}; );
} }
};
this.setState(updatedState); const addJsonModal = (
}
fitTableToWidthIfNeeded() {
const containerWidth = this.container.current?.clientWidth ?? 0;
if (this.totalTableWidth < containerWidth) {
// fit table width if content doesn't fill the width of the container
this.totalTableWidth = containerWidth;
}
this.setState({ fitted: true });
}
addJsonModal(
node: React.ReactNode, node: React.ReactNode,
jsonObject: Record<string, unknown> | unknown[], jsonObject: Record<string, unknown> | unknown[],
jsonString: CellDataType, jsonString: CellDataType,
) { ) => (
return ( <ModalTrigger
<ModalTrigger modalBody={<JSONTree data={jsonObject} theme={getJsonTreeTheme()} />}
modalBody={ modalFooter={
<JSONTree data={jsonObject} theme={this.getJsonTreeTheme()} /> <Button>
} <CopyToClipboard shouldShowText={false} text={jsonString} />
modalFooter={ </Button>
<Button> }
<CopyToClipboard shouldShowText={false} text={jsonString} /> modalTitle={t('Cell content')}
</Button> triggerNode={node}
} />
modalTitle={t('Cell content')} );
triggerNode={node}
/>
);
}
// Parse any numbers from strings so they'll sort correctly // Parse any numbers from strings so they'll sort correctly
parseNumberFromString = (value: string | number | null) => { const parseNumberFromString = (value: string | number | null) => {
if (typeof value === 'string') { if (typeof value === 'string') {
if (ONLY_NUMBER_REGEX.test(value)) { if (ONLY_NUMBER_REGEX.test(value)) {
return parseFloat(value); return parseFloat(value);
@ -479,10 +428,10 @@ class FilterableTable extends PureComponent<
return value; return value;
}; };
sortResults(sortBy: string, descending: boolean) { const sortResults =
return (a: Datum, b: Datum) => { (sortBy: string, descending: boolean) => (a: Datum, b: Datum) => {
const aValue = this.parseNumberFromString(a[sortBy]); const aValue = parseNumberFromString(a[sortBy]);
const bValue = this.parseNumberFromString(b[sortBy]); const bValue = parseNumberFromString(b[sortBy]);
// equal items sort equally // equal items sort equally
if (aValue === bValue) { if (aValue === bValue) {
@ -502,20 +451,18 @@ class FilterableTable extends PureComponent<
} }
return aValue < bValue ? -1 : 1; return aValue < bValue ? -1 : 1;
}; };
}
sortGrid = (label: string) => { const sortGrid = (label: string) => {
this.sort({ sort({
sortBy: label, sortBy: label,
sortDirection: sortDirection:
this.state.sortDirection === SortDirection.DESC || sortDirectionState === SortDirection.DESC || sortByState !== label
this.state.sortBy !== label
? SortDirection.ASC ? SortDirection.ASC
: SortDirection.DESC, : SortDirection.DESC,
}); });
}; };
renderTableHeader({ const renderTableHeader = ({
dataKey, dataKey,
label, label,
sortBy, sortBy,
@ -525,9 +472,9 @@ class FilterableTable extends PureComponent<
label: string; label: string;
sortBy: string; sortBy: string;
sortDirection: SortDirectionType; sortDirection: SortDirectionType;
}) { }) => {
const className = const className =
this.props.expandedColumns.indexOf(label) > -1 expandedColumns.indexOf(label) > -1
? 'header-style-disabled' ? 'header-style-disabled'
: 'header-style'; : 'header-style';
@ -537,9 +484,9 @@ class FilterableTable extends PureComponent<
{sortBy === dataKey && <SortIndicator sortDirection={sortDirection} />} {sortBy === dataKey && <SortIndicator sortDirection={sortDirection} />}
</div> </div>
); );
} };
renderGridCellHeader({ const renderGridCellHeader = ({
columnIndex, columnIndex,
key, key,
style, style,
@ -547,10 +494,10 @@ class FilterableTable extends PureComponent<
columnIndex: number; columnIndex: number;
key: string; key: string;
style: React.CSSProperties; style: React.CSSProperties;
}) { }) => {
const label = this.props.orderedColumnKeys[columnIndex]; const label = orderedColumnKeys[columnIndex];
const className = const className =
this.props.expandedColumns.indexOf(label) > -1 expandedColumns.indexOf(label) > -1
? 'header-style-disabled' ? 'header-style-disabled'
: 'header-style'; : 'header-style';
return ( return (
@ -566,17 +513,17 @@ class FilterableTable extends PureComponent<
className={`${className} grid-cell grid-header-cell`} className={`${className} grid-cell grid-header-cell`}
role="columnheader" role="columnheader"
tabIndex={columnIndex} tabIndex={columnIndex}
onClick={() => this.sortGrid(label)} onClick={() => sortGrid(label)}
> >
{label} {label}
{this.state.sortBy === label && ( {sortByState === label && (
<SortIndicator sortDirection={this.state.sortDirection} /> <SortIndicator sortDirection={sortDirectionState} />
)} )}
</div> </div>
); );
} };
renderGridCell({ const renderGridCell = ({
columnIndex, columnIndex,
key, key,
rowIndex, rowIndex,
@ -586,10 +533,10 @@ class FilterableTable extends PureComponent<
key: string; key: string;
rowIndex: number; rowIndex: number;
style: React.CSSProperties; style: React.CSSProperties;
}) { }) => {
const columnKey = this.props.orderedColumnKeys[columnIndex]; const columnKey = orderedColumnKeys[columnIndex];
const cellData = this.state.displayedList[rowIndex][columnKey]; const cellData = displayedList[rowIndex][columnKey];
const cellText = this.getCellContent({ cellData, columnKey }); const cellText = getCellContent({ cellData, columnKey });
const content = const content =
cellData === null ? <i className="text-muted">{cellText}</i> : cellText; cellData === null ? <i className="text-muted">{cellText}</i> : cellText;
const cellNode = ( const cellNode = (
@ -602,7 +549,7 @@ class FilterableTable extends PureComponent<
? style.top - GRID_POSITION_ADJUSTMENT ? style.top - GRID_POSITION_ADJUSTMENT
: style.top, : style.top,
}} }}
className={`grid-cell ${this.rowClassName({ index: rowIndex })}`} className={`grid-cell ${rowClassName({ index: rowIndex })}`}
> >
<div css={{ width: 'inherit' }}>{content}</div> <div css={{ width: 'inherit' }}>{content}</div>
</div> </div>
@ -610,33 +557,23 @@ class FilterableTable extends PureComponent<
const jsonObject = safeJsonObjectParse(cellData); const jsonObject = safeJsonObjectParse(cellData);
if (jsonObject) { if (jsonObject) {
return this.addJsonModal(cellNode, jsonObject, cellData); return addJsonModal(cellNode, jsonObject, cellData);
} }
return cellNode; return cellNode;
} };
renderGrid() { const renderGrid = () => {
const {
orderedColumnKeys,
overscanColumnCount,
overscanRowCount,
rowHeight,
} = this.props;
let { height } = this.props;
let totalTableHeight = height;
if ( if (
this.container.current && container.current &&
this.totalTableWidth > this.container.current.clientWidth totalTableWidth.current > container.current.clientWidth
) { ) {
// exclude the height of the horizontal scroll bar from the height of the table // exclude the height of the horizontal scroll bar from the height of the table
// and the height of the table container if the content overflows // and the height of the table container if the content overflows
height -= SCROLL_BAR_HEIGHT; totalTableHeight.current -= SCROLL_BAR_HEIGHT;
totalTableHeight -= SCROLL_BAR_HEIGHT;
} }
const getColumnWidth = ({ index }: { index: number }) => const getColumnWidth = ({ index }: { index: number }) =>
this.widthsForColumnsByKey[orderedColumnKeys[index]]; widthsForColumnsByKey[orderedColumnKeys[index]];
// fix height of filterable table // fix height of filterable table
return ( return (
@ -648,7 +585,7 @@ class FilterableTable extends PureComponent<
{({ width }) => ( {({ width }) => (
<div> <div>
<Grid <Grid
cellRenderer={this.renderGridCellHeader} cellRenderer={renderGridCellHeader}
columnCount={orderedColumnKeys.length} columnCount={orderedColumnKeys.length}
columnWidth={getColumnWidth} columnWidth={getColumnWidth}
height={rowHeight} height={rowHeight}
@ -659,14 +596,14 @@ class FilterableTable extends PureComponent<
style={{ overflow: 'hidden' }} style={{ overflow: 'hidden' }}
/> />
<Grid <Grid
cellRenderer={this.renderGridCell} cellRenderer={renderGridCell}
columnCount={orderedColumnKeys.length} columnCount={orderedColumnKeys.length}
columnWidth={getColumnWidth} columnWidth={getColumnWidth}
height={totalTableHeight - rowHeight} height={totalTableHeight.current - rowHeight}
onScroll={onScroll} onScroll={onScroll}
overscanColumnCount={overscanColumnCount} overscanColumnCount={overscanColumnCount}
overscanRowCount={overscanRowCount} overscanRowCount={overscanRowCount}
rowCount={this.list.length} rowCount={list.length}
rowHeight={rowHeight} rowHeight={rowHeight}
width={width} width={width}
/> />
@ -678,86 +615,73 @@ class FilterableTable extends PureComponent<
</ScrollSync> </ScrollSync>
</StyledFilterableTable> </StyledFilterableTable>
); );
} };
renderTableCell({ const renderTableCell = ({
cellData, cellData,
columnKey, columnKey,
}: { }: {
cellData: CellDataType; cellData: CellDataType;
columnKey: string; columnKey: string;
}) { }) => {
const cellNode = this.getCellContent({ cellData, columnKey }); const cellNode = getCellContent({ cellData, columnKey });
const content = const content =
cellData === null ? <i className="text-muted">{cellNode}</i> : cellNode; cellData === null ? <i className="text-muted">{cellNode}</i> : cellNode;
const jsonObject = safeJsonObjectParse(cellData); const jsonObject = safeJsonObjectParse(cellData);
if (jsonObject) { if (jsonObject) {
return this.addJsonModal(cellNode, jsonObject, cellData); return addJsonModal(cellNode, jsonObject, cellData);
} }
return content; return content;
} };
renderTable() { const renderTable = () => {
const { sortBy, sortDirection } = this.state; let sortedAndFilteredList = displayedList;
const {
filterText,
headerHeight,
orderedColumnKeys,
overscanRowCount,
rowHeight,
} = this.props;
let sortedAndFilteredList = this.state.displayedList;
// filter list // filter list
if (filterText) { if (filterText) {
sortedAndFilteredList = sortedAndFilteredList.filter((row: Datum) => sortedAndFilteredList = sortedAndFilteredList.filter((row: Datum) =>
this.hasMatch(filterText, row), hasMatch(filterText, row),
); );
} }
let { height } = this.props;
let totalTableHeight = height;
if ( if (
this.container.current && container.current &&
this.totalTableWidth > this.container.current.clientWidth totalTableWidth.current > container.current.clientWidth
) { ) {
// exclude the height of the horizontal scroll bar from the height of the table // exclude the height of the horizontal scroll bar from the height of the table
// and the height of the table container if the content overflows // and the height of the table container if the content overflows
height -= SCROLL_BAR_HEIGHT; totalTableHeight.current -= SCROLL_BAR_HEIGHT;
totalTableHeight -= SCROLL_BAR_HEIGHT;
} }
const rowGetter = ({ index }: { index: number }) => const rowGetter = ({ index }: { index: number }) =>
this.getDatum(sortedAndFilteredList, index); getDatum(sortedAndFilteredList, index);
return ( return (
<StyledFilterableTable <StyledFilterableTable
className="filterable-table-container" className="filterable-table-container"
ref={this.container} ref={container}
> >
{this.state.fitted && ( {fitted && (
<Table <Table
ref="Table"
headerHeight={headerHeight} headerHeight={headerHeight}
height={totalTableHeight} height={totalTableHeight.current}
overscanRowCount={overscanRowCount} overscanRowCount={overscanRowCount}
rowClassName={this.rowClassName} rowClassName={rowClassName}
rowHeight={rowHeight} rowHeight={rowHeight}
rowGetter={rowGetter} rowGetter={rowGetter}
rowCount={sortedAndFilteredList.length} rowCount={sortedAndFilteredList.length}
sort={this.sort} sort={sort}
sortBy={sortBy} sortBy={sortByState}
sortDirection={sortDirection} sortDirection={sortDirectionState}
width={this.totalTableWidth} width={totalTableWidth.current}
> >
{orderedColumnKeys.map(columnKey => ( {orderedColumnKeys.map(columnKey => (
<Column <Column
cellRenderer={({ cellData }) => cellRenderer={({ cellData }) =>
this.renderTableCell({ cellData, columnKey }) renderTableCell({ cellData, columnKey })
} }
dataKey={columnKey} dataKey={columnKey}
disableSort={false} disableSort={false}
headerRenderer={this.renderTableHeader} headerRenderer={renderTableHeader}
width={this.widthsForColumnsByKey[columnKey]} width={widthsForColumnsByKey[columnKey]}
label={columnKey} label={columnKey}
key={columnKey} key={columnKey}
/> />
@ -766,14 +690,12 @@ class FilterableTable extends PureComponent<
)} )}
</StyledFilterableTable> </StyledFilterableTable>
); );
} };
render() { if (orderedColumnKeys.length > MAX_COLUMNS_FOR_TABLE) {
if (this.props.orderedColumnKeys.length > MAX_COLUMNS_FOR_TABLE) { return renderGrid();
return this.renderGrid();
}
return this.renderTable();
} }
} return renderTable();
};
export default withTheme(FilterableTable); export default FilterableTable;