mirror of
https://github.com/gosticks/DefinitelyTyped.git
synced 2025-10-16 12:05:41 +00:00
* react-table: port v7 types over from react-table repo Since the v7 version of react-table has a completely incompatible interface to v6, and is also no longer including types in its v7 package, this changeset ports over the v7 types and moves the v6 types into a sub-folder. The interface isn't ideal, it depends upon interface merging to be useful. If anyone can offer suggestions as to how to fix this without falling into the recursive definition problems of generating types based upon plugins which themselves change the types that they are being used to define, please let me know. https://gist.github.com/ggascoigne/646e14c9d54258e40588a13aabf0102d * mv utils.d.ts from tsconfig -> OTHER_FILES.txt * test should now pass * fix type for UseRowSelectState.selectedRowPaths * upgrade to beta.28 * upgrade to rc.1 * upgrade to rc.3 * upgrade to rc.9 * update to rc.10, address review comments * Use a generic for prop getters, minor fixes * lint cleanup Also added styles to `TableCommonProps` since that's what it's generally used for in the plugin-hooks. * add intermediate and address remaining review comments. * update to rc.15, add readme Co-authored-by: Roman Komarov <kizmarh@ya.ru>
709 lines
26 KiB
TypeScript
709 lines
26 KiB
TypeScript
import * as React from 'react';
|
|
import * as ReactDOM from 'react-dom';
|
|
|
|
// test heavily based up https://github.com/tannerlinsley/react-table/blob/master/examples/kitchen-sink-controlled/src/App.js
|
|
|
|
import {
|
|
Cell,
|
|
CellProps,
|
|
Column,
|
|
FilterProps,
|
|
FilterValue,
|
|
HeaderGroup,
|
|
HeaderProps,
|
|
Hooks,
|
|
IdType,
|
|
Row,
|
|
useExpanded,
|
|
UseExpandedInstanceProps,
|
|
UseExpandedOptions,
|
|
UseExpandedRowProps,
|
|
UseExpandedState,
|
|
useFilters,
|
|
UseFiltersColumnOptions,
|
|
UseFiltersColumnProps,
|
|
UseFiltersInstanceProps,
|
|
UseFiltersOptions,
|
|
UseFiltersState,
|
|
useGroupBy,
|
|
UseGroupByCellProps,
|
|
UseGroupByColumnOptions,
|
|
UseGroupByColumnProps,
|
|
UseGroupByInstanceProps,
|
|
UseGroupByOptions,
|
|
UseGroupByRowProps,
|
|
UseGroupByState,
|
|
usePagination,
|
|
UsePaginationInstanceProps,
|
|
UsePaginationOptions,
|
|
UsePaginationState,
|
|
useRowSelect,
|
|
UseRowSelectInstanceProps,
|
|
UseRowSelectOptions,
|
|
UseRowSelectRowProps,
|
|
UseRowSelectState,
|
|
useSortBy,
|
|
UseSortByColumnOptions,
|
|
UseSortByColumnProps,
|
|
UseSortByInstanceProps,
|
|
UseSortByOptions,
|
|
UseSortByState,
|
|
useTable,
|
|
} from 'react-table';
|
|
|
|
declare module 'react-table' {
|
|
// take this file as-is, or comment out the sections that don't apply to your plugin configuration
|
|
|
|
interface TableOptions<D extends object>
|
|
extends UseExpandedOptions<D>,
|
|
UseFiltersOptions<D>,
|
|
UseGroupByOptions<D>,
|
|
UsePaginationOptions<D>,
|
|
UseRowSelectOptions<D>,
|
|
UseSortByOptions<D> {
|
|
updateMyData: (rowIndex: number, columnId: string, value: any) => void;
|
|
}
|
|
|
|
interface TableInstance<D extends object = {}>
|
|
extends UseExpandedInstanceProps<D>,
|
|
UseFiltersInstanceProps<D>,
|
|
UseGroupByInstanceProps<D>,
|
|
UsePaginationInstanceProps<D>,
|
|
UseRowSelectInstanceProps<D>,
|
|
UseSortByInstanceProps<D> {
|
|
editable: boolean;
|
|
}
|
|
|
|
interface TableState<D extends object = {}>
|
|
extends UseExpandedState<D>,
|
|
UseFiltersState<D>,
|
|
UseGroupByState<D>,
|
|
UsePaginationState<D>,
|
|
UseRowSelectState<D>,
|
|
UseSortByState<D> {}
|
|
|
|
interface Column<D extends object = {}>
|
|
extends UseFiltersColumnOptions<D>,
|
|
UseGroupByColumnOptions<D>,
|
|
UseSortByColumnOptions<D> {}
|
|
|
|
interface ColumnInstance<D extends object = {}>
|
|
extends UseFiltersColumnProps<D>,
|
|
UseGroupByColumnProps<D>,
|
|
UseSortByColumnProps<D> {}
|
|
|
|
interface Cell<D extends object = {}> extends UseGroupByCellProps<D> {}
|
|
|
|
interface Row<D extends object = {}>
|
|
extends UseExpandedRowProps<D>,
|
|
UseGroupByRowProps<D>,
|
|
UseRowSelectRowProps<D> {}
|
|
}
|
|
|
|
interface Data {
|
|
firstName: string;
|
|
lastName: string;
|
|
age: number;
|
|
visits: number;
|
|
progress: number;
|
|
status: string;
|
|
subRows?: Data[];
|
|
}
|
|
|
|
// Create an editable cell renderer
|
|
const EditableCell = ({
|
|
cell: { value: initialValue },
|
|
row: { index },
|
|
column: { id },
|
|
updateMyData, // This is a custom function that we supplied to our table instance
|
|
editable,
|
|
}: CellProps<Data>) => {
|
|
// We need to keep and update the state of the cell normally
|
|
const [value, setValue] = React.useState(initialValue);
|
|
|
|
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
setValue(e.target.value);
|
|
};
|
|
|
|
// We'll only update the external data when the input is blurred
|
|
const onBlur = () => {
|
|
updateMyData(index, id, value);
|
|
};
|
|
|
|
// If the initialValue is changed externall, sync it up with our state
|
|
React.useEffect(() => {
|
|
setValue(initialValue);
|
|
}, [initialValue]);
|
|
|
|
if (!editable) {
|
|
return `${initialValue}`;
|
|
}
|
|
|
|
return <input value={value} onChange={onChange} onBlur={onBlur} />;
|
|
};
|
|
|
|
// Define a default UI for filtering
|
|
function DefaultColumnFilter({ column: { filterValue, preFilteredRows, setFilter } }: FilterProps<Data>) {
|
|
const count = preFilteredRows.length;
|
|
|
|
return (
|
|
<input
|
|
value={filterValue || ''}
|
|
onChange={e => {
|
|
setFilter(e.target.value || undefined); // Set undefined to remove the filter entirely
|
|
}}
|
|
placeholder={`Search ${count} records...`}
|
|
/>
|
|
);
|
|
}
|
|
|
|
// This is a custom filter UI for selecting
|
|
// a unique option from a list
|
|
function SelectColumnFilter({ column: { filterValue, setFilter, preFilteredRows, id } }: FilterProps<Data>) {
|
|
// Calculate the options for filtering
|
|
// using the preFilteredRows
|
|
const options = React.useMemo(() => {
|
|
const options = new Set<any>();
|
|
preFilteredRows.forEach(row => {
|
|
options.add(row.values[id]);
|
|
});
|
|
return [...Array.from(options.values())];
|
|
}, [id, preFilteredRows]);
|
|
|
|
// Render a multi-select box
|
|
return (
|
|
<select
|
|
value={filterValue}
|
|
onChange={e => {
|
|
setFilter(e.target.value || undefined);
|
|
}}
|
|
>
|
|
<option value="">All</option>
|
|
{options.map((option, i) => (
|
|
<option key={i} value={option}>
|
|
{option}
|
|
</option>
|
|
))}
|
|
</select>
|
|
);
|
|
}
|
|
|
|
// This is a custom filter UI that uses a
|
|
// slider to set the filter value between a column's
|
|
// min and max values
|
|
function SliderColumnFilter({ column: { filterValue, setFilter, preFilteredRows, id } }: FilterProps<Data>) {
|
|
// Calculate the min and max
|
|
// using the preFilteredRows
|
|
|
|
const [min, max] = React.useMemo(() => {
|
|
let min = preFilteredRows.length ? preFilteredRows[0].values[id] : 0;
|
|
let max = preFilteredRows.length ? preFilteredRows[0].values[id] : 0;
|
|
preFilteredRows.forEach(row => {
|
|
min = Math.min(row.values[id], min);
|
|
max = Math.max(row.values[id], max);
|
|
});
|
|
return [min, max];
|
|
}, [id, preFilteredRows]);
|
|
|
|
return (
|
|
<>
|
|
<input
|
|
type="range"
|
|
min={min}
|
|
max={max}
|
|
value={filterValue || min}
|
|
onChange={e => {
|
|
setFilter(parseInt(e.target.value, 10));
|
|
}}
|
|
/>
|
|
<button onClick={() => setFilter(undefined)}>Off</button>
|
|
</>
|
|
);
|
|
}
|
|
|
|
// This is a custom UI for our 'between' or number range
|
|
// filter. It uses two number boxes and filters rows to
|
|
// ones that have values between the two
|
|
function NumberRangeColumnFilter({ column: { filterValue = [], preFilteredRows, setFilter, id } }: FilterProps<Data>) {
|
|
const [min, max] = React.useMemo(() => {
|
|
let min = preFilteredRows.length ? preFilteredRows[0].values[id] : 0;
|
|
let max = preFilteredRows.length ? preFilteredRows[0].values[id] : 0;
|
|
preFilteredRows.forEach(row => {
|
|
min = Math.min(row.values[id], min);
|
|
max = Math.max(row.values[id], max);
|
|
});
|
|
return [min, max];
|
|
}, [id, preFilteredRows]);
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
}}
|
|
>
|
|
<input
|
|
value={filterValue[0] || ''}
|
|
type="number"
|
|
onChange={e => {
|
|
const val = e.target.value;
|
|
setFilter((old: any[] = []) => [val ? parseInt(val, 10) : undefined, old[1]]);
|
|
}}
|
|
placeholder={`Min (${min})`}
|
|
style={{
|
|
width: '70px',
|
|
marginRight: '0.5rem',
|
|
}}
|
|
/>
|
|
to
|
|
<input
|
|
value={filterValue[1] || ''}
|
|
type="number"
|
|
onChange={e => {
|
|
const val = e.target.value;
|
|
setFilter((old: any[] = []) => [old[0], val ? parseInt(val, 10) : undefined]);
|
|
}}
|
|
placeholder={`Max (${max})`}
|
|
style={{
|
|
width: '70px',
|
|
marginLeft: '0.5rem',
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function fuzzyTextFilterFn<T extends object>(rows: Array<Row<T>>, id: IdType<T>, filterValue: FilterValue) {
|
|
// return matchSorter(rows, filterValue, {
|
|
// keys: [(row: Row<any>) => row.values[id]],
|
|
// });
|
|
return rows;
|
|
}
|
|
|
|
// Let the table remove the filter if the string is empty
|
|
fuzzyTextFilterFn.autoRemove = (val: any) => !val;
|
|
|
|
interface Table<T extends object> {
|
|
columns: Array<Column<T>>;
|
|
data: T[];
|
|
updateMyData: any;
|
|
skipPageReset: boolean;
|
|
}
|
|
|
|
// Be sure to pass our updateMyData and the skipPageReset option
|
|
function Table({ columns, data, updateMyData, skipPageReset }: Table<Data>) {
|
|
const filterTypes = React.useMemo(
|
|
() => ({
|
|
// Add a new fuzzyTextFilterFn filter type.
|
|
fuzzyText: fuzzyTextFilterFn,
|
|
// Or, override the default text filter to use
|
|
// "startWith"
|
|
text: (rows: Array<Row<Data>>, id: IdType<Data>, filterValue: FilterValue) => {
|
|
return rows.filter(row => {
|
|
const rowValue = row.values[id];
|
|
return rowValue !== undefined
|
|
? String(rowValue)
|
|
.toLowerCase()
|
|
.startsWith(String(filterValue).toLowerCase())
|
|
: true;
|
|
});
|
|
},
|
|
}),
|
|
[],
|
|
);
|
|
|
|
const defaultColumn = React.useMemo(
|
|
() => ({
|
|
// Let's set up our default Filter UI
|
|
Filter: DefaultColumnFilter,
|
|
// And also our default editable cell
|
|
Cell: EditableCell,
|
|
}),
|
|
[],
|
|
);
|
|
|
|
// Use the state and functions returned from useTable to build your UI
|
|
const {
|
|
getTableProps,
|
|
getTableBodyProps,
|
|
headerGroups,
|
|
prepareRow,
|
|
page, // Instead of using 'rows', we'll use page,
|
|
// which has only the rows for the active page
|
|
|
|
// The rest of these things are super handy, too ;)
|
|
canPreviousPage,
|
|
canNextPage,
|
|
pageOptions,
|
|
pageCount,
|
|
gotoPage,
|
|
nextPage,
|
|
previousPage,
|
|
setPageSize,
|
|
state: { pageIndex, pageSize, groupBy, expanded, filters, selectedRowIds },
|
|
} = useTable<Data>(
|
|
{
|
|
columns,
|
|
data,
|
|
defaultColumn,
|
|
filterTypes,
|
|
// nestExpandedRows: true,
|
|
initialState: { pageIndex: 2 },
|
|
// updateMyData isn't part of the API, but
|
|
// anything we put into these options will
|
|
// automatically be available on the instance.
|
|
// That way we can call this function from our
|
|
// cell renderer!
|
|
updateMyData,
|
|
// We also need to pass this so the page doesn't change
|
|
// when we edit the data, undefined means using the default
|
|
autoResetPage: !skipPageReset,
|
|
},
|
|
useGroupBy,
|
|
useFilters,
|
|
useSortBy,
|
|
useExpanded,
|
|
usePagination,
|
|
useRowSelect,
|
|
(hooks: Hooks<Data>) => {
|
|
hooks.flatColumns.push(columns => [
|
|
{
|
|
id: 'selection',
|
|
// Make this column a groupByBoundary. This ensures that groupBy columns
|
|
// are placed after it
|
|
groupByBoundary: true,
|
|
// The header can use the table's getToggleAllRowsSelectedProps method
|
|
// to render a checkbox
|
|
Header: ({ getToggleAllRowsSelectedProps }: HeaderProps<Data>) => (
|
|
<div>
|
|
<input type="checkbox" {...getToggleAllRowsSelectedProps()} />
|
|
</div>
|
|
),
|
|
// The cell can use the individual row's getToggleRowSelectedProps method
|
|
// to the render a checkbox
|
|
Cell: ({ row }: CellProps<Data>) => (
|
|
<div>
|
|
<input type="checkbox" {...row.getToggleRowSelectedProps()} />
|
|
</div>
|
|
),
|
|
},
|
|
...columns,
|
|
]);
|
|
},
|
|
);
|
|
|
|
// Render the UI for your table
|
|
return (
|
|
<>
|
|
<table {...getTableProps()}>
|
|
<thead>
|
|
{headerGroups.map((headerGroup: HeaderGroup<Data>) => (
|
|
<tr {...headerGroup.getHeaderGroupProps()}>
|
|
{headerGroup.headers.map(column => (
|
|
<th {...column.getHeaderProps()}>
|
|
<div>
|
|
{column.canGroupBy ? (
|
|
// If the column can be grouped, let's add a toggle
|
|
<span {...column.getGroupByToggleProps()}>
|
|
{column.isGrouped ? '🛑 ' : '👊 '}
|
|
</span>
|
|
) : null}
|
|
<span {...column.getSortByToggleProps()}>
|
|
{column.render('Header')}
|
|
{/* Add a sort direction indicator */}
|
|
{column.isSorted ? (column.isSortedDesc ? ' 🔽' : ' 🔼') : ''}
|
|
</span>
|
|
</div>
|
|
{/* Render the columns filter UI */}
|
|
<div>{column.canFilter ? column.render('Filter') : null}</div>
|
|
</th>
|
|
))}
|
|
</tr>
|
|
))}
|
|
</thead>
|
|
<tbody {...getTableBodyProps()}>
|
|
{page.map((row: Row<Data>) => {
|
|
prepareRow(row);
|
|
return (
|
|
<tr {...row.getRowProps()}>
|
|
{row.cells.map((cell: Cell<Data>) => {
|
|
return (
|
|
<td {...cell.getCellProps()}>
|
|
{cell.isGrouped ? (
|
|
<>
|
|
<span {...row.getExpandedToggleProps()}>
|
|
{row.isExpanded ? '👇' : '👉'}
|
|
</span>{' '}
|
|
{cell.render('Cell', { editable: false })} ({row.subRows.length})
|
|
</>
|
|
) : cell.isAggregated ? (
|
|
// If the cell is aggregated, use the Aggregated
|
|
// renderer for cell
|
|
cell.render('Aggregated')
|
|
) : cell.isRepeatedValue ? null : (// For cells with repeated values, render null
|
|
// Otherwise, just render the regular cell
|
|
cell.render('Cell', { editable: true })
|
|
)}
|
|
</td>
|
|
);
|
|
})}
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
{/*
|
|
Pagination can be built however you'd like.
|
|
This is just a very basic UI implementation:
|
|
*/}
|
|
<div className="pagination">
|
|
<button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
|
|
{'<<'}
|
|
</button>{' '}
|
|
<button onClick={previousPage} disabled={!canPreviousPage}>
|
|
{'<'}
|
|
</button>{' '}
|
|
<button onClick={nextPage} disabled={!canNextPage}>
|
|
{'>'}
|
|
</button>{' '}
|
|
<button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
|
|
{'>>'}
|
|
</button>{' '}
|
|
<span>
|
|
Page{' '}
|
|
<strong>
|
|
{pageIndex + 1} of {pageOptions.length}
|
|
</strong>{' '}
|
|
</span>
|
|
<span>
|
|
| Go to page:{' '}
|
|
<input
|
|
type="number"
|
|
defaultValue={pageIndex + 1}
|
|
onChange={e => {
|
|
const page = e.target.value ? Number(e.target.value) - 1 : 0;
|
|
gotoPage(page);
|
|
}}
|
|
style={{ width: '100px' }}
|
|
/>
|
|
</span>{' '}
|
|
<select
|
|
value={pageSize}
|
|
onChange={e => {
|
|
setPageSize(Number(e.target.value));
|
|
}}
|
|
>
|
|
{[10, 20, 30, 40, 50].map(pageSize => (
|
|
<option key={pageSize} value={pageSize}>
|
|
Show {pageSize}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<pre>
|
|
<code>
|
|
{JSON.stringify(
|
|
{
|
|
pageIndex,
|
|
pageSize,
|
|
pageCount,
|
|
canNextPage,
|
|
canPreviousPage,
|
|
groupBy,
|
|
expanded,
|
|
filters,
|
|
selectedRowIds,
|
|
},
|
|
null,
|
|
2,
|
|
)}
|
|
</code>
|
|
</pre>
|
|
</>
|
|
);
|
|
}
|
|
|
|
// Define a custom filter filter function!
|
|
function filterGreaterThan(rows: Array<Row<any>>, id: Array<IdType<any>>, filterValue: FilterValue) {
|
|
return rows.filter(row => {
|
|
const rowValue = row.values[id[0]];
|
|
return rowValue >= filterValue;
|
|
});
|
|
}
|
|
|
|
// This is an autoRemove method on the filter function that
|
|
// when given the new filter value and returns true, the filter
|
|
// will be automatically removed. Normally this is just an undefined
|
|
// check, but here, we want to remove the filter if it's not a number
|
|
filterGreaterThan.autoRemove = (val: any) => typeof val !== 'number';
|
|
|
|
// This is a custom aggregator that
|
|
// takes in an array of values and
|
|
// returns the rounded median
|
|
function roundedMedian(values: any[]) {
|
|
let min = values[0] || '';
|
|
let max = values[0] || '';
|
|
|
|
values.forEach(value => {
|
|
min = Math.min(min, value);
|
|
max = Math.max(max, value);
|
|
});
|
|
|
|
return Math.round((min + max) / 2);
|
|
}
|
|
|
|
const Component = (props: {}) => {
|
|
const startingData: Data[] = [
|
|
{ firstName: 'plastic', lastName: 'leather', age: 1, visits: 87, progress: 53, status: 'relationship' },
|
|
{ firstName: 'eggs', lastName: 'quartz', age: 13, visits: 78, progress: 82, status: 'complicated' },
|
|
{ firstName: 'wash', lastName: 'wrench', age: 29, visits: 75, progress: 49, status: 'single' },
|
|
{ firstName: 'introduction', lastName: 'impression', age: 2, visits: 35, progress: 51, status: 'relationship' },
|
|
{ firstName: 'steel', lastName: 'difference', age: 9, visits: 64, progress: 94, status: 'complicated' },
|
|
{ firstName: 'snakes', lastName: 'corn', age: 17, visits: 55, progress: 47, status: 'relationship' },
|
|
{ firstName: 'ocean', lastName: 'definition', age: 26, visits: 17, progress: 22, status: 'single' },
|
|
{ firstName: 'drawing', lastName: 'fifth', age: 15, visits: 84, progress: 12, status: 'complicated' },
|
|
{ firstName: 'silver', lastName: 'riddle', age: 15, visits: 59, progress: 24, status: 'relationship' },
|
|
{ firstName: 'surprise', lastName: 'zinc', age: 23, visits: 7, progress: 48, status: 'single' },
|
|
{ firstName: 'riddle', lastName: 'information', age: 2, visits: 63, progress: 3, status: 'complicated' },
|
|
];
|
|
const columns = React.useMemo(
|
|
() => [
|
|
{
|
|
id: 'selection',
|
|
// Make this column a groupByBoundary. This ensures that groupBy columns
|
|
// are placed after it
|
|
groupByBoundary: true,
|
|
// The header can use the table's getToggleAllRowsSelectedProps method
|
|
// to render a checkbox
|
|
Header: ({ getToggleAllRowsSelectedProps }: HeaderProps<Data>) => (
|
|
<div>
|
|
<input type="checkbox" {...getToggleAllRowsSelectedProps()} />
|
|
</div>
|
|
),
|
|
// The cell can use the individual row's getToggleRowSelectedProps method
|
|
// to the render a checkbox
|
|
Cell: ({ row }: CellProps<Data>) => (
|
|
<div>
|
|
<input type="checkbox" {...row.getToggleRowSelectedProps()} />
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
Header: 'Name',
|
|
columns: [
|
|
{
|
|
Header: 'First Name',
|
|
accessor: 'firstName',
|
|
// Use a two-stage aggregator here to first
|
|
// count the total rows being aggregated,
|
|
// then sum any of those counts if they are
|
|
// aggregated further
|
|
aggregate: ['sum', 'count'],
|
|
Aggregated: ({ cell: { value } }: CellProps<Data>) => `${value} Names`,
|
|
},
|
|
{
|
|
Header: 'Last Name',
|
|
accessor: 'lastName',
|
|
// Use our custom `fuzzyText` filter on this column
|
|
filter: 'fuzzyText',
|
|
// Use another two-stage aggregator here to
|
|
// first count the UNIQUE values from the rows
|
|
// being aggregated, then sum those counts if
|
|
// they are aggregated further
|
|
aggregate: ['sum', 'uniqueCount'],
|
|
Aggregated: ({ cell: { value } }: CellProps<Data>) => `${value} Unique Names`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
Header: 'Info',
|
|
columns: [
|
|
{
|
|
Header: 'Age',
|
|
accessor: 'age',
|
|
Filter: SliderColumnFilter,
|
|
filter: 'equals',
|
|
// Aggregate the average age of visitors
|
|
aggregate: 'average',
|
|
Aggregated: ({ cell: { value } }: CellProps<Data>) => `${value} (avg)`,
|
|
},
|
|
{
|
|
Header: 'Visits',
|
|
accessor: 'visits',
|
|
Filter: NumberRangeColumnFilter,
|
|
filter: 'between',
|
|
// Aggregate the sum of all visits
|
|
aggregate: 'sum',
|
|
Aggregated: ({ cell: { value } }: CellProps<Data>) => `${value} (total)`,
|
|
},
|
|
{
|
|
Header: 'Status',
|
|
accessor: 'status',
|
|
Filter: SelectColumnFilter,
|
|
filter: 'includes',
|
|
},
|
|
{
|
|
Header: 'Profile Progress',
|
|
accessor: 'progress',
|
|
Filter: SliderColumnFilter,
|
|
filter: filterGreaterThan,
|
|
// Use our custom roundedMedian aggregator
|
|
aggregate: roundedMedian,
|
|
Aggregated: ({ cell: { value } }: CellProps<Data>) => `${value} (med)`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
[],
|
|
);
|
|
|
|
const [data, setData] = React.useState<Data[]>(() => startingData);
|
|
const [originalData] = React.useState(data);
|
|
|
|
// We need to keep the table from resetting the pageIndex when we
|
|
// Update data. So we can keep track of that flag with a ref.
|
|
const skipPageResetRef = React.useRef(false);
|
|
|
|
// When our cell renderer calls updateMyData, we'll use
|
|
// the rowIndex, columnId and new value to update the
|
|
// original data
|
|
const updateMyData = (rowIndex: number, columnId: string, value: any) => {
|
|
// We also turn on the flag to not reset the page
|
|
skipPageResetRef.current = true;
|
|
setData(old =>
|
|
old.map((row, index) => {
|
|
if (index === rowIndex) {
|
|
return {
|
|
...row,
|
|
[columnId]: value,
|
|
};
|
|
}
|
|
return row;
|
|
}),
|
|
);
|
|
};
|
|
|
|
// After data chagnes, we turn the flag back off
|
|
// so that if data actually changes when we're not
|
|
// editing it, the page is reset
|
|
React.useEffect(() => {
|
|
skipPageResetRef.current = false;
|
|
}, [data]);
|
|
|
|
// Let's add a data resetter/randomizer to help
|
|
// illustrate that flow...
|
|
const resetData = () => {
|
|
// Don't reset the page when we do this
|
|
skipPageResetRef.current = true;
|
|
setData(originalData);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<button onClick={resetData}>Reset Data</button>
|
|
<Table columns={columns} data={data} updateMyData={updateMyData} skipPageReset={skipPageResetRef.current} />
|
|
</>
|
|
);
|
|
};
|
|
|
|
ReactDOM.render(<Component />, document.getElementById('root'));
|