Support providing records directly to DataTable

Summary: This diff allows using a DataTable without setting up a full datasource, which is simpler in case the dataset is small and/or fixed. For an continuously growing dataset a DataSource should still be set up.

Reviewed By: jknoxville

Differential Revision: D28037897

fbshipit-source-id: 2e56b1970e19f967c3752a78737b8f7a3f934b87
This commit is contained in:
Michel Weststrate
2021-04-27 14:52:34 -07:00
committed by Facebook GitHub Bot
parent 9a6e84fedd
commit 00e4d2440d
2 changed files with 154 additions and 3 deletions

View File

@@ -49,7 +49,7 @@ import {debounce} from 'lodash';
interface DataTableProps<T = any> {
columns: DataTableColumn<T>[];
dataSource: DataSource<T, any, any>;
autoScroll?: boolean;
extraActions?: React.ReactElement;
onSelect?(record: T | undefined, records: T[]): void;
@@ -61,6 +61,12 @@ interface DataTableProps<T = any> {
onContextMenu?: (selection: undefined | T) => React.ReactElement;
}
type DataTableInput<T = any> =
| {dataSource: DataSource<T, any, any>}
| {
records: T[];
};
export type DataTableColumn<T = any> = {
key: keyof T & string;
// possible future extension: getValue(row) (and free-form key) to support computed columns
@@ -94,9 +100,10 @@ export interface RenderContext<T = any> {
}
export function DataTable<T extends object>(
props: DataTableProps<T>,
props: DataTableInput<T> & DataTableProps<T>,
): React.ReactElement {
const {dataSource, onRowStyle, onSelect, onCopyRows, onContextMenu} = props;
const {onRowStyle, onSelect, onCopyRows, onContextMenu} = props;
const dataSource = normalizeDataSourceInput(props);
useAssertStableRef(dataSource, 'dataSource');
useAssertStableRef(onRowStyle, 'onRowStyle');
useAssertStableRef(props.onSelect, 'onRowSelect');
@@ -445,6 +452,42 @@ export function DataTable<T extends object>(
);
}
/* eslint-disable react-hooks/rules-of-hooks */
function normalizeDataSourceInput<T>(props: DataTableInput<T>): DataSource<T> {
if ('dataSource' in props) {
return props.dataSource;
}
if ('records' in props) {
const [dataSource] = useState(() => {
const ds = new DataSource<T>(undefined);
syncRecordsToDataSource(ds, props.records);
return ds;
});
useEffect(() => {
syncRecordsToDataSource(dataSource, props.records);
}, [dataSource, props.records]);
return dataSource;
}
throw new Error(
`Either the 'dataSource' or 'records' prop should be provided to DataTable`,
);
}
/* eslint-enable */
function syncRecordsToDataSource<T>(ds: DataSource<T>, records: T[]) {
const startTime = Date.now();
ds.clear();
// TODO: optimize in the case we're only dealing with appends or replacements
records.forEach((r) => ds.append(r));
const duration = Math.abs(Date.now() - startTime);
if (duration > 50 || records.length > 1000) {
console.warn(
"The 'records' props is only intended to be used on small datasets. Please use a 'dataSource' instead. See createDataSource for details: https://fbflipper.com/docs/extending/flipper-plugin#createdatasource",
);
}
}
function emptyRenderer(dataSource: DataSource<any>) {
return <EmptyTable dataSource={dataSource} />;
}