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:
committed by
Facebook GitHub Bot
parent
9a6e84fedd
commit
00e4d2440d
@@ -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} />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {DataTable, DataTableColumn} from '../DataTable';
|
||||
import {render} from '@testing-library/react';
|
||||
|
||||
type Todo = {
|
||||
title: string;
|
||||
done: boolean;
|
||||
};
|
||||
|
||||
function createTestRecords(): Todo[] {
|
||||
return [
|
||||
{
|
||||
title: 'test DataTable',
|
||||
done: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const columns: DataTableColumn[] = [
|
||||
{
|
||||
key: 'title',
|
||||
wrap: false,
|
||||
},
|
||||
{
|
||||
key: 'done',
|
||||
wrap: false,
|
||||
},
|
||||
];
|
||||
|
||||
test('update and append', async () => {
|
||||
let records = createTestRecords();
|
||||
const rendering = render(
|
||||
<DataTable records={records} columns={columns} _testHeight={400} />,
|
||||
);
|
||||
{
|
||||
const elem = await rendering.findAllByText('test DataTable');
|
||||
expect(elem.length).toBe(1);
|
||||
expect(elem[0].parentElement).toMatchInlineSnapshot(`
|
||||
<div
|
||||
class="css-1k3kr6b-TableBodyRowContainer e1luu51r1"
|
||||
>
|
||||
<div
|
||||
class="css-744e08-TableBodyColumnContainer e1luu51r0"
|
||||
>
|
||||
test DataTable
|
||||
</div>
|
||||
<div
|
||||
class="css-744e08-TableBodyColumnContainer e1luu51r0"
|
||||
>
|
||||
true
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
function rerender() {
|
||||
rendering.rerender(
|
||||
<DataTable records={records} columns={columns} _testHeight={400} />,
|
||||
);
|
||||
}
|
||||
|
||||
// append
|
||||
{
|
||||
records = [
|
||||
...records,
|
||||
{
|
||||
title: 'Drink coffee',
|
||||
done: false,
|
||||
},
|
||||
];
|
||||
rerender();
|
||||
const elem = await rendering.findAllByText('Drink coffee');
|
||||
expect(elem.length).toBe(1);
|
||||
}
|
||||
|
||||
// update
|
||||
{
|
||||
records = [
|
||||
{
|
||||
title: 'DataTable tested',
|
||||
done: false,
|
||||
},
|
||||
...records.slice(1),
|
||||
];
|
||||
rerender();
|
||||
const elem = await rendering.findAllByText('DataTable tested');
|
||||
expect(elem.length).toBe(1);
|
||||
expect(rendering.queryByText('test DataTable')).toBeNull();
|
||||
}
|
||||
|
||||
// remove
|
||||
{
|
||||
records = [records[1]];
|
||||
rerender();
|
||||
const elem = await rendering.findAllByText('Drink coffee');
|
||||
expect(elem.length).toBe(1);
|
||||
expect(rendering.queryByText('DataTable tested')).toBeNull();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user