3

I have a component that creates a react table which works as expected when there is just one page involved:

const ReactTable = (props) => {
    const {data, columns, viewState, updateViewState} = props;

    const defaultColumn = React.useMemo(() => ({
        Filter: DefaultColumnFilter,
        updateFiltering: (column, newValue) => updateFiltering(viewState, updateViewState, column, newValue),
        filter: 'standard',
        canResize: true}),[]);

    const {filters, filterable, groupBy, expanded, page} = viewState;

    const tableInstance = useTable(
        {
            columns,
            data,
            defaultColumn,
            autoResetExpanded: false,
            groupByFn: (rows, columnId) => {
                const getKey = value => value.label ? value.label: value;
                return rows.reduce((prev, row) => {
                    const resKey = `${getKey(row.values[columnId])}`;
                    prev[resKey] = Array.isArray(prev[resKey]) ? prev[resKey] : [];
                    prev[resKey].push(row);
                    return prev}, {})},
            filterTypes: {'standard': applyFilter},
            pageSize: page.size,
            initialState: {filters, groupBy, expanded, pageIndex: page.index}},
        useFilters,
        useGroupBy,
        useExpanded,
        usePagination,
        useRowSelect,
        addSelectColumn);

    const {getTableProps, getTableBodyProps, setGroupBy} = tableInstance;

    React.useEffect(
        () => updateViewStateFromTable(viewState, tableInstance, updateViewState));

    if (!isEqual(groupBy, tableInstance.state.groupBy)) setGroupBy(groupBy);

    const headerRows = getHeaderRows(tableInstance, viewState, updateViewState);
    const dataRows = getDataRows(tableInstance);
    const filterRow = getFilterRow(tableInstance, viewState);

    console.log('ReactTable', {props, tableInstance})
    return (
        <div>
            <table className="ReactTable -highlight rt-table"
                   {...getTableProps()}>
                <thead className="rt-thead -header">
                    {headerRows}
                </thead>
                <tbody className="rt-tbody" {...getTableBodyProps()}>
                    {(filterable)? filterRow:null}
                    {dataRows}
                </tbody>
                <PaginationFoot {...{viewState, tableInstance}} />
            </table>
            <h2>viewState:</h2>
            <pre>
                <code>
                    {JSON.stringify({viewState}, null, 4)}
                </code>
            </pre>
        </div>)};

export default ReactTable;

Unfortunately, if I change the page index or size on one page, it affects all other pages as well.

The "viewState" is my construct used to track and persist the filter, pagination, expansion, column selection, grouping, and sorting (and to permit users to create their own custom named view states).

It appears that the internal state of the table is being destructively modified because the viewState copies show the correct pagination data (but are then updated to the incorrect shared values after render).

I tried using useControlledState to work directly into/out of the viewState but this causes errors because it is attempting to update the state during the render operation.

Paul Whipp
  • 16,028
  • 4
  • 42
  • 54

1 Answers1

0

As the table state is a state, the behaviour is technically correct. The trouble here is that when the data and viewState changes, I want to effectively remount the react table component (or re-initialize its state).

There is a 'table page' with the component on it and its properties are changing so that there is a different table specification and data content being supplied to it.

The routing routes each /table/:tableName to the same TablePage which provides the data and viewState to its ReactTable component. Thus the useTable internal state is being re-used for each table and this causes the problem.

A verbose workaround simply adds a component for each table page (rather than just changing the data) changing my routing table from this:

const routeMap = [
  {path: '/', component: Dashboard, allowAny},
  {path: '/login', component: LoginOrOut, allowAny},

  {path: "/table/:tableName", component: TablePage},
  {path: "/tables", component: Tables},
  {path: "/new/:modelType", component: RoutedModel, action: 'new'},
  ...

to this:

const routeMap = [
  {path: '/', component: Dashboard, allowAny},
  {path: '/login', component: LoginOrOut, allowAny},

  ...tableNames.map(tableName =>
      ({path: `/table/${tableName}`, component: props => <TablePage tableName={tableName} {...props} />})),

  {path: "/tables", component: Tables},
  {path: "/new/:modelType", component: RoutedModel, action: 'new'},
  ...

This feels clunky because I'm creating a larger DOM so that the components can keep their own state but it appears performant.

Paul Whipp
  • 16,028
  • 4
  • 42
  • 54