diff --git a/.size-snapshot.json b/.size-snapshot.json index 353606d..c44aefd 100644 --- a/.size-snapshot.json +++ b/.size-snapshot.json @@ -1,20 +1,20 @@ { "dist/index.js": { - "bundled": 113244, - "minified": 52531, - "gzipped": 13840 + "bundled": 131178, + "minified": 61448, + "gzipped": 15699 }, "dist/index.es.js": { - "bundled": 112307, - "minified": 51695, - "gzipped": 13674, + "bundled": 130236, + "minified": 60607, + "gzipped": 15525, "treeshaked": { "rollup": { "code": 80, "import_statements": 21 }, "webpack": { - "code": 8471 + "code": 8457 } } }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 65bfc2f..02ea1e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - Renamed `instance.flatColumns` to `instance.allColumns` which now accumulates ALL columns created for the table, visible or not. - Added the `instance.visibleColumns` object - Fix an issue where `useAsyncDebounce` would crash when passed arguments -- Started development on the `usePivotColumns` plugin, which can be tested currently using the `_UNSTABLE_usePivoteColumns` export. +- Started development on the `usePivotColumns` plugin, which can be tested currently using the `_UNSTABLE_usePivotColumns` export. - Renamed `cell.isRepeatedValue` to `cell.isPlaceholder` - Removed `useConsumeHookGetter` as it was inefficient most of the time and noisy - All hooks are now "consumed" right after main plugin functions are run. This means that any attempt to add a plugin after that will result in a runtime error (for good reason, since using hook points should not be a conditional or async operation) @@ -25,6 +25,20 @@ - Fixed an issue where `useGlobalFilter` could be placed after `usePagination` - Added the sort order direction as a parameter to the sortMethod function - Fixed an issue where user filter types were not being referenced correctly +- Renamed the `row.getExpandedToggleProps` to `row.getToggleRowExpandedProps` +- Renamed the `row.toggleExpanded` method to `row.toggleRowExpanded` +- Added the `instance.toggleRowExpanded` and `instance.toggleAllRowsExpanded` methods +- Added the `instance.getToggleAllRowsExpandedProps` prop getter +- Added the `instance.filteredRowsById` property +- Added the `instance.preFilteredRowsById` property +- useFilters now properly updates the `instance.rowsById` property +- Added the `instance.globalFilteredRowsById` property +- Added the `instance.preGlobalFilteredRowsById` property +- useGlobalFilter now properly updates the `instance.rowsById` property +- Added the `instance.nonGroupedFlatRows` property +- Added the `instance.nonGroupedRowsById` property +- Added the `instance.onlyGroupedFlatRows` property +- Added the `instance.onlyGroupedRowsById` property ## 7.0.0-rc.15 diff --git a/docs/api/useExpanded.md b/docs/api/useExpanded.md index 49934bd..a49eed1 100644 --- a/docs/api/useExpanded.md +++ b/docs/api/useExpanded.md @@ -41,6 +41,13 @@ The following properties are available on the table instance returned from `useT - `rows: Array` - An array of **expanded** rows. +- `toggleRowExpanded: Function(rowId, isExpanded?)` + - A function to toggle whether a row is expanded or not. The `isExpanded` boolean is optional, otherwise it will be a true toggle action +- `toggleAllRowsExpanded: Function(isExpanded?)` + - A function to toggle whether all of the rows in the table are expanded or not. The `isExpanded` boolean is optional, otherwise it will be a true toggle action +- `isAllRowsExpanded` +- `getToggleAllRowsExpandedProps: Function(userProps) => props` + - A prop getter function that returns all necessary props for an element to be clicked and toggle all of the rows expanded or not. ### Row Properties @@ -48,7 +55,7 @@ The following additional properties are available on every `row` object returned - `isExpanded: Bool` - If `true`, this row is in an expanded state. -- `toggleExpanded: Function(?isExpanded: Bool) => void` +- `toggleRowExpanded: Function(?isExpanded: Bool) => void` - This function will toggle the expanded state of a row between `true` and `false` or, if an `isExpanded` boolean is passed to the function, it will be set as the new `isExpanded` value. - Rows with a hard-coded `manualExpandedKey` (defaults to `expanded`) set to `true` are not affected by this function or the internal expanded state. diff --git a/docs/api/useTable.md b/docs/api/useTable.md index 9272552..87dba27 100644 --- a/docs/api/useTable.md +++ b/docs/api/useTable.md @@ -93,11 +93,6 @@ The following options are supported on any column object you can pass to `column - Receives the table instance and cell model as props - Must return valid JSX - This function (or component) is primarily used for formatting the column value, eg. If your column accessor returns a date object, you can use a `Cell` function to format that date to a readable format. -- `show: Bool` - - Optional - - Defaults to `undefined` - - If set to `true`, will take priority over any state in `hiddenColumns` and force this column to be visible at all times. - - If set to `false` will take priority over any state in `hiddenColumns` and force this column to be hidden at all times. - `width: Int` - Optional - Defaults to `150` diff --git a/examples/expanding/src/App.js b/examples/expanding/src/App.js index 2d11219..44cd7f0 100644 --- a/examples/expanding/src/App.js +++ b/examples/expanding/src/App.js @@ -88,14 +88,18 @@ function App() { () => [ { // Build our expander column - Header: () => null, // No header, please id: 'expander', // Make sure it has an ID + Header: ({ getToggleAllRowsExpandedProps, isAllRowsExpanded }) => ( + + {isAllRowsExpanded ? '👇' : '👉'} + + ), Cell: ({ row }) => - // Use the row.canExpand and row.getExpandedToggleProps prop getter + // Use the row.canExpand and row.getToggleRowExpandedProps prop getter // to build the toggle for expanding a row row.canExpand ? ( - + {row.isExpanded ? '👇' : '👉'} {' '} {cell.render('Cell')} ({row.subRows.length}) diff --git a/examples/kitchen-sink-controlled/src/App.js b/examples/kitchen-sink-controlled/src/App.js index e815f6b..351f44a 100644 --- a/examples/kitchen-sink-controlled/src/App.js +++ b/examples/kitchen-sink-controlled/src/App.js @@ -378,7 +378,7 @@ function Table({ columns, data, updateMyData, skipPageReset }) { {cell.isGrouped ? ( // If it's a grouped cell, add an expander and row count <> - + {row.isExpanded ? '👇' : '👉'} {' '} {cell.render('Cell', { editable: false })} ( diff --git a/examples/kitchen-sink-controlled/yarn.lock b/examples/kitchen-sink-controlled/yarn.lock index c43e4c8..6edf4bb 100644 --- a/examples/kitchen-sink-controlled/yarn.lock +++ b/examples/kitchen-sink-controlled/yarn.lock @@ -8095,10 +8095,10 @@ react-scripts@3.0.1: optionalDependencies: fsevents "2.0.6" -react-table@next: - version "7.0.0-alpha.7" - resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.0.0-alpha.7.tgz#0cb6da6f32adb397e68505b7cdd4880d15d73017" - integrity sha512-oXE9RRkE2CFk1OloNCSTPQ9qxOdujgkCoW5b/srbJsBog/ySkWuozBTQkxH1wGNmnSxGyTrTxJqXdXPQam7VAw== +react-table@latest: + version "7.0.0-rc.15" + resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.0.0-rc.15.tgz#bb855e4e2abbb4aaf0ed2334404a41f3ada8e13a" + integrity sha512-ofMOlgrioHhhvHjvjsQkxvfQzU98cqwy6BjPGNwhLN1vhgXeWi0mUGreaCPvRenEbTiXsQbMl4k3Xmx3Mut8Rw== react@^16.8.6: version "16.8.6" diff --git a/examples/kitchen-sink/src/App.js b/examples/kitchen-sink/src/App.js index 738450b..aec9d80 100644 --- a/examples/kitchen-sink/src/App.js +++ b/examples/kitchen-sink/src/App.js @@ -380,7 +380,7 @@ function Table({ columns, data, updateMyData, skipReset }) { {cell.isGrouped ? ( // If it's a grouped cell, add an expander and row count <> - + {row.isExpanded ? '👇' : '👉'} {' '} {cell.render('Cell', { editable: false })} ( diff --git a/examples/pivoting/src/App.js b/examples/pivoting/src/App.js index 9302291..eac6263 100644 --- a/examples/pivoting/src/App.js +++ b/examples/pivoting/src/App.js @@ -4,7 +4,7 @@ import { useTable, useGroupBy, useExpanded, - _UNSTABLE_usePivoteColumns, + _UNSTABLE_usePivotColumns, } from 'react-table' import dayjs from 'dayjs' import localizedFormat from 'dayjs/plugin/localizedFormat' @@ -98,8 +98,8 @@ function Table({ columns, data }) { data, }, useGroupBy, - _UNSTABLE_usePivoteColumns, - useExpanded // useGroupBy and _UNSTABLE_usePivoteColumns would be pretty useless without useExpanded ;) + _UNSTABLE_usePivotColumns, + useExpanded // useGroupBy and _UNSTABLE_usePivotColumns would be pretty useless without useExpanded ;) ) // We don't want to render all of the rows for this example, so cap @@ -197,7 +197,7 @@ function Table({ columns, data }) { {cell.isGrouped ? ( <> - + {row.isExpanded ? '👇' : '👉'} {cell.render('Cell')}{' '} ({row.subRows.length}) diff --git a/examples/sub-components/src/App.js b/examples/sub-components/src/App.js index b88cc49..81801a1 100644 --- a/examples/sub-components/src/App.js +++ b/examples/sub-components/src/App.js @@ -120,9 +120,9 @@ function App() { id: 'expander', // It needs an ID Cell: ({ row }) => ( // Use Cell to render an expander for each row. - // We can use the getExpandedToggleProps prop-getter + // We can use the getToggleRowExpandedProps prop-getter // to build the expander. - + {row.isExpanded ? '👇' : '👉'} ), diff --git a/src/hooks/useTable.js b/src/hooks/useTable.js index 2b231d7..639ec7d 100755 --- a/src/hooks/useTable.js +++ b/src/hooks/useTable.js @@ -1,11 +1,12 @@ import React from 'react' // + import { linkColumnStructure, flattenColumns, assignColumnAccessor, - accessRowsForColumn, + unpreparedAccessWarning, makeHeaderGroups, decorateColumn, dedupeBy, @@ -488,10 +489,12 @@ function calculateHeaderWidths(headers, left = 0) { header.totalLeft = left if (subHeaders && subHeaders.length) { - const [totalMinWidth, totalWidth, totalMaxWidth, totalFlexWidth] = calculateHeaderWidths( - subHeaders, - left - ) + const [ + totalMinWidth, + totalWidth, + totalMaxWidth, + totalFlexWidth, + ] = calculateHeaderWidths(subHeaders, left) header.totalMinWidth = totalMinWidth header.totalWidth = totalWidth header.totalMaxWidth = totalMaxWidth @@ -516,3 +519,94 @@ function calculateHeaderWidths(headers, left = 0) { return [sumTotalMinWidth, sumTotalWidth, sumTotalMaxWidth, sumTotalFlexWidth] } + +function accessRowsForColumn({ + data, + rows, + flatRows, + rowsById, + column, + getRowId, + getSubRows, + accessValueHooks, + getInstance, +}) { + // Access the row's data column-by-column + // We do it this way so we can incrementally add materialized + // columns after the first pass and avoid excessive looping + const accessRow = (originalRow, rowIndex, depth = 0, parent, parentRows) => { + // Keep the original reference around + const original = originalRow + + const id = getRowId(originalRow, rowIndex, parent) + + let row = rowsById[id] + + // If the row hasn't been created, let's make it + if (!row) { + row = { + id, + original, + index: rowIndex, + depth, + cells: [{}], // This is a dummy cell + } + + // Override common array functions (and the dummy cell's getCellProps function) + // to show an error if it is accessed without calling prepareRow + row.cells.map = unpreparedAccessWarning + row.cells.filter = unpreparedAccessWarning + row.cells.forEach = unpreparedAccessWarning + row.cells[0].getCellProps = unpreparedAccessWarning + + // Create the cells and values + row.values = {} + + // Push this row into the parentRows array + parentRows.push(row) + // Keep track of every row in a flat array + flatRows.push(row) + // Also keep track of every row by its ID + rowsById[id] = row + + // Get the original subrows + row.originalSubRows = getSubRows(originalRow, rowIndex) + + // Then recursively access them + if (row.originalSubRows) { + const subRows = [] + row.originalSubRows.forEach((d, i) => + accessRow(d, i, depth + 1, row, subRows) + ) + // Keep the new subRows array on the row + row.subRows = subRows + } + } else if (row.subRows) { + // If the row exists, then it's already been accessed + // Keep recursing, but don't worry about passing the + // accumlator array (those rows already exist) + row.originalSubRows.forEach((d, i) => accessRow(d, i, depth + 1, row)) + } + + // If the column has an accessor, use it to get a value + if (column.accessor) { + row.values[column.id] = column.accessor(originalRow, rowIndex, row) + } + + // Allow plugins to manipulate the column value + row.values[column.id] = reduceHooks( + accessValueHooks, + row.values[column.id], + { + row, + column, + instance: getInstance(), + }, + true + ) + } + + data.forEach((originalRow, rowIndex) => + accessRow(originalRow, rowIndex, 0, undefined, rows) + ) +} diff --git a/src/index.js b/src/index.js index 334a341..992f933 100755 --- a/src/index.js +++ b/src/index.js @@ -6,7 +6,7 @@ export { useGlobalFilter } from './plugin-hooks/useGlobalFilter' export { useGroupBy } from './plugin-hooks/useGroupBy' export { useSortBy } from './plugin-hooks/useSortBy' export { usePagination } from './plugin-hooks/usePagination' -export { usePivotColumns as _UNSTABLE_usePivoteColumns } from './plugin-hooks/usePivotColumns' +export { _UNSTABLE_usePivotColumns } from './plugin-hooks/_UNSTABLE_usePivotColumns' export { useRowSelect } from './plugin-hooks/useRowSelect' export { useRowState } from './plugin-hooks/useRowState' export { useColumnOrder } from './plugin-hooks/useColumnOrder' diff --git a/src/plugin-hooks/usePivotColumns.js b/src/plugin-hooks/_UNSTABLE_usePivotColumns.js similarity index 98% rename from src/plugin-hooks/usePivotColumns.js rename to src/plugin-hooks/_UNSTABLE_usePivotColumns.js index ee3d812..f9c7b4d 100644 --- a/src/plugin-hooks/usePivotColumns.js +++ b/src/plugin-hooks/_UNSTABLE_usePivotColumns.js @@ -14,7 +14,7 @@ import { flattenColumns, getFirstDefined } from '../utils' actions.resetPivot = 'resetPivot' actions.togglePivot = 'togglePivot' -export const usePivotColumns = hooks => { +export const _UNSTABLE_usePivotColumns = hooks => { hooks.getPivotToggleProps = [defaultGetPivotToggleProps] hooks.stateReducers.push(reducer) hooks.useInstanceAfterData.push(useInstanceAfterData) @@ -28,7 +28,7 @@ export const usePivotColumns = hooks => { hooks.prepareRow.push(prepareRow) } -usePivotColumns.pluginName = 'usePivotColumns' +_UNSTABLE_usePivotColumns.pluginName = 'usePivotColumns' const defaultPivotColumns = [] diff --git a/src/plugin-hooks/tests/useExpanded.test.js b/src/plugin-hooks/tests/useExpanded.test.js index 3fcb3f9..d2bcd2c 100644 --- a/src/plugin-hooks/tests/useExpanded.test.js +++ b/src/plugin-hooks/tests/useExpanded.test.js @@ -75,7 +75,7 @@ function App() { cursor: 'pointer', paddingLeft: `${row.depth * 2}rem`, }} - onClick={() => row.toggleExpanded()} + onClick={() => row.toggleRowExpanded()} > {row.isExpanded ? 'Collapse' : 'Expand'} Row {row.id} diff --git a/src/plugin-hooks/tests/useGroupBy.test.js b/src/plugin-hooks/tests/useGroupBy.test.js index b373621..6ecbfa8 100644 --- a/src/plugin-hooks/tests/useGroupBy.test.js +++ b/src/plugin-hooks/tests/useGroupBy.test.js @@ -113,7 +113,7 @@ function Table({ columns, data }) { style={{ cursor: 'pointer', }} - onClick={() => row.toggleExpanded()} + onClick={() => row.toggleRowExpanded()} > {row.isExpanded ? '👇' : '👉'} diff --git a/src/plugin-hooks/tests/useRowState.test.js b/src/plugin-hooks/tests/useRowState.test.js index e2600ba..3a9ccc5 100644 --- a/src/plugin-hooks/tests/useRowState.test.js +++ b/src/plugin-hooks/tests/useRowState.test.js @@ -2,7 +2,6 @@ import React from 'react' import { render, fireEvent } from '../../../test-utils/react-testing' import { useTable } from '../../hooks/useTable' import { useRowState } from '../useRowState' -import { useGlobalFilter } from '../useGlobalFilter' const data = [ { @@ -67,8 +66,7 @@ function Table({ columns, data }) { initialRowStateAccessor: () => ({ count: 0 }), initialCellStateAccessor: () => ({ count: 0 }), }, - useRowState, - useGlobalFilter + useRowState ) return ( diff --git a/src/plugin-hooks/useExpanded.js b/src/plugin-hooks/useExpanded.js index d2891b5..ff78534 100755 --- a/src/plugin-hooks/useExpanded.js +++ b/src/plugin-hooks/useExpanded.js @@ -5,19 +5,18 @@ import { expandRows } from '../utils' import { useGetLatest, actions, - functionalUpdate, useMountedLayoutEffect, makePropGetter, } from '../publicUtils' // Actions -actions.toggleExpanded = 'toggleExpanded' -actions.toggleAllExpanded = 'toggleAllExpanded' -actions.setExpanded = 'setExpanded' actions.resetExpanded = 'resetExpanded' +actions.toggleRowExpanded = 'toggleRowExpanded' +actions.toggleAllRowsExpanded = 'toggleAllRowsExpanded' export const useExpanded = hooks => { - hooks.getExpandedToggleProps = [defaultGetExpandedToggleProps] + hooks.getToggleAllRowsExpandedProps = [defaultGetToggleAllRowsExpandedProps] + hooks.getToggleRowExpandedProps = [defaultGetToggleRowExpandedProps] hooks.stateReducers.push(reducer) hooks.useInstance.push(useInstance) hooks.prepareRow.push(prepareRow) @@ -25,17 +24,29 @@ export const useExpanded = hooks => { useExpanded.pluginName = 'useExpanded' -const defaultGetExpandedToggleProps = (props, { row }) => [ +const defaultGetToggleAllRowsExpandedProps = (props, { instance }) => [ props, { onClick: e => { - e.persist() - row.toggleExpanded() + instance.toggleAllRowsExpanded() }, style: { cursor: 'pointer', }, - title: 'Toggle Expanded', + title: 'Toggle All Rows Expanded', + }, +] + +const defaultGetToggleRowExpandedProps = (props, { row }) => [ + props, + { + onClick: () => { + row.toggleRowExpanded() + }, + style: { + cursor: 'pointer', + }, + title: 'Toggle Row Expanded', }, ] @@ -55,14 +66,32 @@ function reducer(state, action, previousState, instance) { } } - if (action.type === actions.setExpanded) { + if (action.type === actions.toggleAllRowsExpanded) { + const { value } = action + const { isAllRowsExpanded, rowsById } = instance + + const expandAll = typeof value !== 'undefined' ? value : !isAllRowsExpanded + + if (expandAll) { + const expanded = {} + + Object.keys(rowsById).forEach(rowId => { + expanded[rowId] = true + }) + + return { + ...state, + expanded, + } + } + return { ...state, - expanded: functionalUpdate(action.expanded, state.expanded), + expanded: {}, } } - if (action.type === actions.toggleExpanded) { + if (action.type === actions.toggleRowExpanded) { const { id, value: setExpanded } = action const exists = state.expanded[id] @@ -93,16 +122,28 @@ function useInstance(instance) { const { data, rows, + rowsById, manualExpandedKey = 'expanded', paginateExpandedRows = true, expandSubRows = true, autoResetExpanded = true, + getHooks, state: { expanded }, dispatch, } = instance const getAutoResetExpanded = useGetLatest(autoResetExpanded) + let isAllRowsExpanded = Boolean( + Object.keys(rowsById).length && Object.keys(expanded).length + ) + + if (isAllRowsExpanded) { + if (Object.keys(rowsById).some(id => !expanded[id])) { + isAllRowsExpanded = false + } + } + // Bypass any effects from firing when this changes useMountedLayoutEffect(() => { if (getAutoResetExpanded()) { @@ -110,13 +151,18 @@ function useInstance(instance) { } }, [dispatch, data]) - const toggleExpanded = React.useCallback( + const toggleRowExpanded = React.useCallback( (id, value) => { - dispatch({ type: actions.toggleExpanded, id, value }) + dispatch({ type: actions.toggleRowExpanded, id, value }) }, [dispatch] ) + const toggleAllRowsExpanded = React.useCallback( + value => dispatch({ type: actions.toggleAllRowsExpanded, value }), + [dispatch] + ) + const expandedRows = React.useMemo(() => { if (paginateExpandedRows) { return expandRows(rows, { manualExpandedKey, expanded, expandSubRows }) @@ -129,20 +175,30 @@ function useInstance(instance) { expanded, ]) + const getInstance = useGetLatest(instance) + + const getToggleAllRowsExpandedProps = makePropGetter( + getHooks().getToggleAllRowsExpandedProps, + { instance: getInstance() } + ) + Object.assign(instance, { preExpandedRows: rows, expandedRows, rows: expandedRows, - toggleExpanded, expandedDepth, + isAllRowsExpanded, + toggleRowExpanded, + toggleAllRowsExpanded, + getToggleAllRowsExpandedProps, }) } function prepareRow(row, { instance: { getHooks }, instance }) { - row.toggleExpanded = set => instance.toggleExpanded(row.id, set) + row.toggleRowExpanded = set => instance.toggleRowExpanded(row.id, set) - row.getExpandedToggleProps = makePropGetter( - getHooks().getExpandedToggleProps, + row.getToggleRowExpandedProps = makePropGetter( + getHooks().getToggleRowExpandedProps, { instance, row, diff --git a/src/plugin-hooks/useFilters.js b/src/plugin-hooks/useFilters.js index 0c3dc04..c52e8f0 100755 --- a/src/plugin-hooks/useFilters.js +++ b/src/plugin-hooks/useFilters.js @@ -108,7 +108,9 @@ function reducer(state, action, previousState, instance) { filterTypes ) - if (shouldAutoRemoveFilter(filterMethod.autoRemove, filter.value, column)) { + if ( + shouldAutoRemoveFilter(filterMethod.autoRemove, filter.value, column) + ) { return false } return true @@ -122,6 +124,7 @@ function useInstance(instance) { data, rows, flatRows, + rowsById, allColumns, filterTypes: userFilterTypes, manualFilters, @@ -175,12 +178,17 @@ function useInstance(instance) { column.filterValue = found && found.value }) - const [filteredRows, filteredFlatRows] = React.useMemo(() => { + const [ + filteredRows, + filteredFlatRows, + filteredRowsById, + ] = React.useMemo(() => { if (manualFilters || !filters.length) { - return [rows, flatRows] + return [rows, flatRows, rowsById] } const filteredFlatRows = [] + const filteredRowsById = {} // Filters top level and nested rows const filterRows = (rows, depth = 0) => { @@ -231,6 +239,7 @@ function useInstance(instance) { // would be required to do that recursion in some scenarios filteredRows = filteredRows.map(row => { filteredFlatRows.push(row) + filteredRowsById[row.id] = row if (!row.subRows) { return row } @@ -246,8 +255,16 @@ function useInstance(instance) { return filteredRows } - return [filterRows(rows), filteredFlatRows] - }, [manualFilters, filters, rows, flatRows, allColumns, userFilterTypes]) + return [filterRows(rows), filteredFlatRows, filteredRowsById] + }, [ + manualFilters, + filters, + rows, + flatRows, + rowsById, + allColumns, + userFilterTypes, + ]) React.useMemo(() => { // Now that each filtered column has it's partially filtered rows, @@ -275,10 +292,13 @@ function useInstance(instance) { Object.assign(instance, { preFilteredRows: rows, preFilteredFlatRows: flatRows, + preFilteredRowsById: rowsById, filteredRows, filteredFlatRows, + filteredRowsById, rows: filteredRows, flatRows: filteredFlatRows, + rowsById: filteredRowsById, setFilter, setAllFilters, }) diff --git a/src/plugin-hooks/useGlobalFilter.js b/src/plugin-hooks/useGlobalFilter.js index ff3f64c..d8df44c 100644 --- a/src/plugin-hooks/useGlobalFilter.js +++ b/src/plugin-hooks/useGlobalFilter.js @@ -61,6 +61,7 @@ function useInstance(instance) { data, rows, flatRows, + rowsById, allColumns, filterTypes: userFilterTypes, globalFilter, @@ -88,12 +89,17 @@ function useInstance(instance) { // cache for each row group (top-level rows, and each row's recursive subrows) // This would make multi-filtering a lot faster though. Too far? - const [globalFilteredRows, globalFilteredFlatRows] = React.useMemo(() => { + const [ + globalFilteredRows, + globalFilteredFlatRows, + globalFilteredRowsById, + ] = React.useMemo(() => { if (manualGlobalFilter || typeof globalFilterValue === 'undefined') { - return [rows, flatRows] + return [rows, flatRows, rowsById] } const filteredFlatRows = [] + const filteredRowsById = {} const filterMethod = getFilterMethod( globalFilter, @@ -114,6 +120,7 @@ function useInstance(instance) { globalFilterValue ).map(row => { filteredFlatRows.push(row) + filteredRowsById[row.id] = row return { ...row, @@ -125,15 +132,16 @@ function useInstance(instance) { }) } - return [filterRows(rows), filteredFlatRows] + return [filterRows(rows), filteredFlatRows, filteredRowsById] }, [ manualGlobalFilter, + globalFilterValue, globalFilter, userFilterTypes, rows, flatRows, + rowsById, allColumns, - globalFilterValue, ]) const getAutoResetGlobalFilter = useGetLatest(autoResetGlobalFilter) @@ -147,10 +155,13 @@ function useInstance(instance) { Object.assign(instance, { preGlobalFilteredRows: rows, preGlobalFilteredFlatRows: flatRows, + preGlobalFilteredRowsById: rowsById, globalFilteredRows, globalFilteredFlatRows, + globalFilteredRowsById, rows: globalFilteredRows, flatRows: globalFilteredFlatRows, + rowsById: globalFilteredRowsById, setGlobalFilter, }) } diff --git a/src/plugin-hooks/useGroupBy.js b/src/plugin-hooks/useGroupBy.js index c826cf1..682e4a3 100755 --- a/src/plugin-hooks/useGroupBy.js +++ b/src/plugin-hooks/useGroupBy.js @@ -13,6 +13,9 @@ import { useGetLatest, } from '../publicUtils' +const emptyArray = [] +const emptyObject = {} + // Actions actions.resetGroupBy = 'resetGroupBy' actions.toggleGroupBy = 'toggleGroupBy' @@ -119,6 +122,7 @@ function useInstance(instance) { data, rows, flatRows, + rowsById, allColumns, flatHeaders, groupByFn = defaultGroupByFn, @@ -179,9 +183,25 @@ function useInstance(instance) { ) }) - const [groupedRows, groupedFlatRows] = React.useMemo(() => { + const [ + groupedRows, + groupedFlatRows, + groupedRowsById, + onlyGroupedFlatRows, + onlyGroupedRowsById, + nonGroupedFlatRows, + nonGroupedRowsById, + ] = React.useMemo(() => { if (manualGroupBy || !groupBy.length) { - return [rows, flatRows] + return [ + rows, + flatRows, + rowsById, + emptyArray, + emptyObject, + flatRows, + rowsById, + ] } // Ensure that the list of filtered columns exist @@ -252,6 +272,11 @@ function useInstance(instance) { } let groupedFlatRows = [] + const groupedRowsById = {} + const onlyGroupedFlatRows = [] + const onlyGroupedRowsById = {} + const nonGroupedFlatRows = [] + const nonGroupedRowsById = {} // Recursively group the data const groupUpRecursively = (rows, depth = 0, parentId) => { @@ -293,7 +318,17 @@ function useInstance(instance) { index, } - groupedFlatRows.push(row, ...subRows) + subRows.forEach(subRow => { + groupedFlatRows.push(subRow) + groupedRowsById[subRow.id] = subRow + if (subRow.isGrouped) { + onlyGroupedFlatRows.push(subRow) + onlyGroupedRowsById[subRow.id] = subRow + } else { + nonGroupedFlatRows.push(subRow) + nonGroupedRowsById[subRow.id] = subRow + } + }) return row } @@ -304,13 +339,34 @@ function useInstance(instance) { const groupedRows = groupUpRecursively(rows) + groupedRows.forEach(subRow => { + groupedFlatRows.push(subRow) + groupedRowsById[subRow.id] = subRow + if (subRow.isGrouped) { + onlyGroupedFlatRows.push(subRow) + onlyGroupedRowsById[subRow.id] = subRow + } else { + nonGroupedFlatRows.push(subRow) + nonGroupedRowsById[subRow.id] = subRow + } + }) + // Assign the new data - return [groupedRows, groupedFlatRows] + return [ + groupedRows, + groupedFlatRows, + groupedRowsById, + onlyGroupedFlatRows, + onlyGroupedRowsById, + nonGroupedFlatRows, + nonGroupedRowsById, + ] }, [ manualGroupBy, groupBy, rows, flatRows, + rowsById, allColumns, userAggregations, groupByFn, @@ -327,10 +383,17 @@ function useInstance(instance) { Object.assign(instance, { preGroupedRows: rows, preGroupedFlatRow: flatRows, + preGroupedRowsById: rowsById, groupedRows, groupedFlatRows, + groupedRowsById, + onlyGroupedFlatRows, + onlyGroupedRowsById, + nonGroupedFlatRows, + nonGroupedRowsById, rows: groupedRows, flatRows: groupedFlatRows, + rowsById: groupedRowsById, toggleGroupBy, }) } diff --git a/src/plugin-hooks/usePagination.js b/src/plugin-hooks/usePagination.js index c1ec924..c651884 100755 --- a/src/plugin-hooks/usePagination.js +++ b/src/plugin-hooks/usePagination.js @@ -77,7 +77,15 @@ function useInstance(instance) { pageCount: userPageCount, paginateExpandedRows = true, expandSubRows = true, - state: { pageSize, pageIndex, expanded, globalFilter, filters, groupBy, sortBy }, + state: { + pageSize, + pageIndex, + expanded, + globalFilter, + filters, + groupBy, + sortBy, + }, dispatch, data, manualPagination, @@ -103,10 +111,10 @@ function useInstance(instance) { }, [ dispatch, manualPagination ? null : data, - manualPagination || manualGlobalFilter ? null : globalFilter, - manualPagination || manualFilters ? null : filters, - manualPagination || manualGroupBy ? null : groupBy, - manualPagination || manualSortBy ? null : sortBy, + manualGlobalFilter ? null : globalFilter, + manualFilters ? null : filters, + manualGroupBy ? null : groupBy, + manualSortBy ? null : sortBy, ]) const pageCount = manualPagination diff --git a/src/plugin-hooks/useRowSelect.js b/src/plugin-hooks/useRowSelect.js index 3e0a2eb..5407310 100644 --- a/src/plugin-hooks/useRowSelect.js +++ b/src/plugin-hooks/useRowSelect.js @@ -86,7 +86,11 @@ function reducer(state, action, previousState, instance) { if (action.type === actions.toggleAllRowsSelected) { const { value: setSelected } = action - const { isAllRowsSelected, flatRowsById } = instance + const { + isAllRowsSelected, + rowsById, + nonGroupedRowsById = rowsById, + } = instance const selectAll = typeof setSelected !== 'undefined' ? setSelected : !isAllRowsSelected @@ -94,7 +98,7 @@ function reducer(state, action, previousState, instance) { if (selectAll) { const selectedRowIds = {} - Object.keys(flatRowsById).forEach(rowId => { + Object.keys(nonGroupedRowsById).forEach(rowId => { selectedRowIds[rowId] = true }) @@ -112,12 +116,12 @@ function reducer(state, action, previousState, instance) { if (action.type === actions.toggleRowSelected) { const { id, value: setSelected } = action - const { flatGroupedRowsById, selectSubRows = true } = instance + const { rowsById, selectSubRows = true } = instance // Join the ids of deep rows // to make a key, then manage all of the keys // in a flat object - const row = flatGroupedRowsById[id] + const row = rowsById[id] const isSelected = row.isSelected const shouldExist = typeof setSelected !== 'undefined' ? setSelected : !isSelected @@ -129,7 +133,7 @@ function reducer(state, action, previousState, instance) { let newSelectedRowIds = { ...state.selectedRowIds } const handleRowById = id => { - const row = flatGroupedRowsById[id] + const row = rowsById[id] if (!row.isGrouped) { if (!isSelected && shouldExist) { @@ -159,7 +163,8 @@ function useInstance(instance) { rows, getHooks, plugins, - flatRows, + rowsById, + nonGroupedRowsById = rowsById, autoResetSelectedRows = true, state: { selectedRowIds }, selectSubRows = true, @@ -173,20 +178,6 @@ function useInstance(instance) { [] ) - const [flatRowsById, flatGroupedRowsById] = React.useMemo(() => { - const all = {} - const grouped = {} - - flatRows.forEach(row => { - if (!row.isGrouped) { - all[row.id] = row - } - grouped[row.id] = row - }) - - return [all, grouped] - }, [flatRows]) - const selectedFlatRows = React.useMemo(() => { const selectedFlatRows = [] @@ -206,11 +197,11 @@ function useInstance(instance) { }, [rows, selectSubRows, selectedRowIds]) let isAllRowsSelected = Boolean( - Object.keys(flatRowsById).length && Object.keys(selectedRowIds).length + Object.keys(nonGroupedRowsById).length && Object.keys(selectedRowIds).length ) if (isAllRowsSelected) { - if (Object.keys(flatRowsById).some(id => !selectedRowIds[id])) { + if (Object.keys(nonGroupedRowsById).some(id => !selectedRowIds[id])) { isAllRowsSelected = false } } @@ -229,8 +220,7 @@ function useInstance(instance) { ) const toggleRowSelected = React.useCallback( - (id, value) => - dispatch({ type: actions.toggleRowSelected, id, value }), + (id, value) => dispatch({ type: actions.toggleRowSelected, id, value }), [dispatch] ) @@ -242,13 +232,11 @@ function useInstance(instance) { ) Object.assign(instance, { - flatRowsById, - flatGroupedRowsById, selectedFlatRows, + isAllRowsSelected, toggleRowSelected, toggleAllRowsSelected, getToggleAllRowsSelectedProps, - isAllRowsSelected, }) } diff --git a/src/plugin-hooks/useSortBy.js b/src/plugin-hooks/useSortBy.js index fde7708..a75319d 100755 --- a/src/plugin-hooks/useSortBy.js +++ b/src/plugin-hooks/useSortBy.js @@ -180,6 +180,7 @@ function useInstance(instance) { const { data, rows, + flatRows, allColumns, orderByFn = defaultOrderByFn, sortTypes: userSortTypes, @@ -194,7 +195,7 @@ function useInstance(instance) { autoResetSortBy = true, } = instance - ensurePluginOrder(plugins, ['useFilters'], 'useSortBy', []) + ensurePluginOrder(plugins, ['useFilters'], 'useSortBy', ['useExpanded']) // Updates sorting based on a columnId, desc flag and multi flag const toggleSortBy = React.useCallback( @@ -249,11 +250,13 @@ function useInstance(instance) { column.isSortedDesc = column.isSorted ? columnSort.desc : undefined }) - const sortedRows = React.useMemo(() => { + const [sortedRows, sortedFlatRows] = React.useMemo(() => { if (manualSortBy || !sortBy.length) { - return rows + return [rows, flatRows] } + const sortedFlatRows = [] + // Filter out sortBys that correspond to non existing columns const availableSortBy = sortBy.filter(sort => allColumns.find(col => col.id === sort.id) @@ -314,6 +317,7 @@ function useInstance(instance) { // If there are sub-rows, sort them sortedData.forEach(row => { + sortedFlatRows.push(row) if (!row.subRows || row.subRows.length <= 1) { return } @@ -323,8 +327,16 @@ function useInstance(instance) { return sortedData } - return sortData(rows) - }, [manualSortBy, sortBy, rows, allColumns, orderByFn, userSortTypes]) + return [sortData(rows), sortedFlatRows] + }, [ + manualSortBy, + sortBy, + rows, + flatRows, + allColumns, + orderByFn, + userSortTypes, + ]) const getAutoResetSortBy = useGetLatest(autoResetSortBy) @@ -336,8 +348,11 @@ function useInstance(instance) { Object.assign(instance, { preSortedRows: rows, + preSortedFlatRows: flatRows, sortedRows, + sortedFlatRows, rows: sortedRows, + flatRows: sortedFlatRows, toggleSortBy, }) } diff --git a/src/utils.js b/src/utils.js index 02fe310..26958be 100755 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,5 @@ import React from 'react' -import { defaultColumn, reduceHooks } from './publicUtils' +import { defaultColumn } from './publicUtils' // Find the depth of the columns export function findMaxDepth(columns, depth = 0) { @@ -88,97 +88,6 @@ export function decorateColumn(column, userDefaultColumn) { return column } -export function accessRowsForColumn({ - data, - rows, - flatRows, - rowsById, - column, - getRowId, - getSubRows, - accessValueHooks, - getInstance, -}) { - // Access the row's data column-by-column - // We do it this way so we can incrementally add materialized - // columns after the first pass and avoid excessive looping - const accessRow = (originalRow, rowIndex, depth = 0, parent, parentRows) => { - // Keep the original reference around - const original = originalRow - - const id = getRowId(originalRow, rowIndex, parent) - - let row = rowsById[id] - - // If the row hasn't been created, let's make it - if (!row) { - row = { - id, - original, - index: rowIndex, - depth, - cells: [{}], // This is a dummy cell - } - - // Override common array functions (and the dummy cell's getCellProps function) - // to show an error if it is accessed without calling prepareRow - row.cells.map = unpreparedAccessWarning - row.cells.filter = unpreparedAccessWarning - row.cells.forEach = unpreparedAccessWarning - row.cells[0].getCellProps = unpreparedAccessWarning - - // Create the cells and values - row.values = {} - - // Push this row into the parentRows array - parentRows.push(row) - // Keep track of every row in a flat array - flatRows.push(row) - // Also keep track of every row by its ID - rowsById[id] = row - - // Get the original subrows - row.originalSubRows = getSubRows(originalRow, rowIndex) - - // Then recursively access them - if (row.originalSubRows) { - const subRows = [] - row.originalSubRows.forEach((d, i) => - accessRow(d, i, depth + 1, row, subRows) - ) - // Keep the new subRows array on the row - row.subRows = subRows - } - } else if (row.subRows) { - // If the row exists, then it's already been accessed - // Keep recursing, but don't worry about passing the - // accumlator array (those rows already exist) - row.originalSubRows.forEach((d, i) => accessRow(d, i, depth + 1, row)) - } - - // If the column has an accessor, use it to get a value - if (column.accessor) { - row.values[column.id] = column.accessor(originalRow, rowIndex, row) - } - - // Allow plugins to manipulate the column value - row.values[column.id] = reduceHooks( - accessValueHooks, - row.values[column.id], - { - row, - column, - instance: getInstance(), - }, - true - ) - } - - data.forEach((originalRow, rowIndex) => - accessRow(originalRow, rowIndex, 0, undefined, rows) - ) -} - // Build the header groups from the bottom up export function makeHeaderGroups(allColumns, defaultColumn) { const headerGroups = []