Normalize API method names and row modesl, add expandAll functionality including prop getter

This commit is contained in:
Tanner Linsley 2020-02-15 12:43:09 -07:00
parent 2bf99aaea0
commit 083e81dc81
26 changed files with 386 additions and 204 deletions

View File

@ -1,20 +1,20 @@
{ {
"dist/index.js": { "dist/index.js": {
"bundled": 113244, "bundled": 131178,
"minified": 52531, "minified": 61448,
"gzipped": 13840 "gzipped": 15699
}, },
"dist/index.es.js": { "dist/index.es.js": {
"bundled": 112307, "bundled": 130236,
"minified": 51695, "minified": 60607,
"gzipped": 13674, "gzipped": 15525,
"treeshaked": { "treeshaked": {
"rollup": { "rollup": {
"code": 80, "code": 80,
"import_statements": 21 "import_statements": 21
}, },
"webpack": { "webpack": {
"code": 8471 "code": 8457
} }
} }
}, },

View File

@ -7,7 +7,7 @@
- Renamed `instance.flatColumns` to `instance.allColumns` which now accumulates ALL columns created for the table, visible or not. - Renamed `instance.flatColumns` to `instance.allColumns` which now accumulates ALL columns created for the table, visible or not.
- Added the `instance.visibleColumns` object - Added the `instance.visibleColumns` object
- Fix an issue where `useAsyncDebounce` would crash when passed arguments - 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` - Renamed `cell.isRepeatedValue` to `cell.isPlaceholder`
- Removed `useConsumeHookGetter` as it was inefficient most of the time and noisy - 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) - 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` - Fixed an issue where `useGlobalFilter` could be placed after `usePagination`
- Added the sort order direction as a parameter to the sortMethod function - Added the sort order direction as a parameter to the sortMethod function
- Fixed an issue where user filter types were not being referenced correctly - 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 ## 7.0.0-rc.15

View File

@ -41,6 +41,13 @@ The following properties are available on the table instance returned from `useT
- `rows: Array<Row>` - `rows: Array<Row>`
- An array of **expanded** rows. - 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 ### Row Properties
@ -48,7 +55,7 @@ The following additional properties are available on every `row` object returned
- `isExpanded: Bool` - `isExpanded: Bool`
- If `true`, this row is in an expanded state. - 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. - 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. - Rows with a hard-coded `manualExpandedKey` (defaults to `expanded`) set to `true` are not affected by this function or the internal expanded state.

View File

@ -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 - Receives the table instance and cell model as props
- Must return valid JSX - 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. - 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` - `width: Int`
- Optional - Optional
- Defaults to `150` - Defaults to `150`

View File

@ -88,14 +88,18 @@ function App() {
() => [ () => [
{ {
// Build our expander column // Build our expander column
Header: () => null, // No header, please
id: 'expander', // Make sure it has an ID id: 'expander', // Make sure it has an ID
Header: ({ getToggleAllRowsExpandedProps, isAllRowsExpanded }) => (
<span {...getToggleAllRowsExpandedProps()}>
{isAllRowsExpanded ? '👇' : '👉'}
</span>
),
Cell: ({ row }) => 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 // to build the toggle for expanding a row
row.canExpand ? ( row.canExpand ? (
<span <span
{...row.getExpandedToggleProps({ {...row.getToggleRowExpandedProps({
style: { style: {
// We can even use the row.depth property // We can even use the row.depth property
// and paddingLeft to indicate the depth // and paddingLeft to indicate the depth

View File

@ -97,7 +97,7 @@ function Table({ columns, data }) {
return ( return (
<span <span
{...row.getExpandedToggleProps({ {...row.getToggleRowExpandedProps({
style: { style: {
// We can even use the row.depth property // We can even use the row.depth property
// and paddingLeft to indicate the depth // and paddingLeft to indicate the depth

View File

@ -103,7 +103,7 @@ function Table({ columns, data }) {
{cell.isGrouped ? ( {cell.isGrouped ? (
// If it's a grouped cell, add an expander and row count // If it's a grouped cell, add an expander and row count
<> <>
<span {...row.getExpandedToggleProps()}> <span {...row.getToggleRowExpandedProps()}>
{row.isExpanded ? '👇' : '👉'} {row.isExpanded ? '👇' : '👉'}
</span>{' '} </span>{' '}
{cell.render('Cell')} ({row.subRows.length}) {cell.render('Cell')} ({row.subRows.length})

View File

@ -378,7 +378,7 @@ function Table({ columns, data, updateMyData, skipPageReset }) {
{cell.isGrouped ? ( {cell.isGrouped ? (
// If it's a grouped cell, add an expander and row count // If it's a grouped cell, add an expander and row count
<> <>
<span {...row.getExpandedToggleProps()}> <span {...row.getToggleRowExpandedProps()}>
{row.isExpanded ? '👇' : '👉'} {row.isExpanded ? '👇' : '👉'}
</span>{' '} </span>{' '}
{cell.render('Cell', { editable: false })} ( {cell.render('Cell', { editable: false })} (

View File

@ -8095,10 +8095,10 @@ react-scripts@3.0.1:
optionalDependencies: optionalDependencies:
fsevents "2.0.6" fsevents "2.0.6"
react-table@next: react-table@latest:
version "7.0.0-alpha.7" version "7.0.0-rc.15"
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.0.0-alpha.7.tgz#0cb6da6f32adb397e68505b7cdd4880d15d73017" resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.0.0-rc.15.tgz#bb855e4e2abbb4aaf0ed2334404a41f3ada8e13a"
integrity sha512-oXE9RRkE2CFk1OloNCSTPQ9qxOdujgkCoW5b/srbJsBog/ySkWuozBTQkxH1wGNmnSxGyTrTxJqXdXPQam7VAw== integrity sha512-ofMOlgrioHhhvHjvjsQkxvfQzU98cqwy6BjPGNwhLN1vhgXeWi0mUGreaCPvRenEbTiXsQbMl4k3Xmx3Mut8Rw==
react@^16.8.6: react@^16.8.6:
version "16.8.6" version "16.8.6"

View File

@ -380,7 +380,7 @@ function Table({ columns, data, updateMyData, skipReset }) {
{cell.isGrouped ? ( {cell.isGrouped ? (
// If it's a grouped cell, add an expander and row count // If it's a grouped cell, add an expander and row count
<> <>
<span {...row.getExpandedToggleProps()}> <span {...row.getToggleRowExpandedProps()}>
{row.isExpanded ? '👇' : '👉'} {row.isExpanded ? '👇' : '👉'}
</span>{' '} </span>{' '}
{cell.render('Cell', { editable: false })} ( {cell.render('Cell', { editable: false })} (

View File

@ -4,7 +4,7 @@ import {
useTable, useTable,
useGroupBy, useGroupBy,
useExpanded, useExpanded,
_UNSTABLE_usePivoteColumns, _UNSTABLE_usePivotColumns,
} from 'react-table' } from 'react-table'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import localizedFormat from 'dayjs/plugin/localizedFormat' import localizedFormat from 'dayjs/plugin/localizedFormat'
@ -98,8 +98,8 @@ function Table({ columns, data }) {
data, data,
}, },
useGroupBy, useGroupBy,
_UNSTABLE_usePivoteColumns, _UNSTABLE_usePivotColumns,
useExpanded // useGroupBy and _UNSTABLE_usePivoteColumns would be pretty useless without useExpanded ;) 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 // We don't want to render all of the rows for this example, so cap
@ -197,7 +197,7 @@ function Table({ columns, data }) {
<td {...cell.getCellProps()}> <td {...cell.getCellProps()}>
{cell.isGrouped ? ( {cell.isGrouped ? (
<> <>
<span {...row.getExpandedToggleProps()}> <span {...row.getToggleRowExpandedProps()}>
{row.isExpanded ? '👇' : '👉'} {cell.render('Cell')}{' '} {row.isExpanded ? '👇' : '👉'} {cell.render('Cell')}{' '}
({row.subRows.length}) ({row.subRows.length})
</span> </span>

View File

@ -120,9 +120,9 @@ function App() {
id: 'expander', // It needs an ID id: 'expander', // It needs an ID
Cell: ({ row }) => ( Cell: ({ row }) => (
// Use Cell to render an expander for each 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. // to build the expander.
<span {...row.getExpandedToggleProps()}> <span {...row.getToggleRowExpandedProps()}>
{row.isExpanded ? '👇' : '👉'} {row.isExpanded ? '👇' : '👉'}
</span> </span>
), ),

View File

@ -1,11 +1,12 @@
import React from 'react' import React from 'react'
// //
import { import {
linkColumnStructure, linkColumnStructure,
flattenColumns, flattenColumns,
assignColumnAccessor, assignColumnAccessor,
accessRowsForColumn, unpreparedAccessWarning,
makeHeaderGroups, makeHeaderGroups,
decorateColumn, decorateColumn,
dedupeBy, dedupeBy,
@ -488,10 +489,12 @@ function calculateHeaderWidths(headers, left = 0) {
header.totalLeft = left header.totalLeft = left
if (subHeaders && subHeaders.length) { if (subHeaders && subHeaders.length) {
const [totalMinWidth, totalWidth, totalMaxWidth, totalFlexWidth] = calculateHeaderWidths( const [
subHeaders, totalMinWidth,
left totalWidth,
) totalMaxWidth,
totalFlexWidth,
] = calculateHeaderWidths(subHeaders, left)
header.totalMinWidth = totalMinWidth header.totalMinWidth = totalMinWidth
header.totalWidth = totalWidth header.totalWidth = totalWidth
header.totalMaxWidth = totalMaxWidth header.totalMaxWidth = totalMaxWidth
@ -516,3 +519,94 @@ function calculateHeaderWidths(headers, left = 0) {
return [sumTotalMinWidth, sumTotalWidth, sumTotalMaxWidth, sumTotalFlexWidth] 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)
)
}

View File

@ -6,7 +6,7 @@ export { useGlobalFilter } from './plugin-hooks/useGlobalFilter'
export { useGroupBy } from './plugin-hooks/useGroupBy' export { useGroupBy } from './plugin-hooks/useGroupBy'
export { useSortBy } from './plugin-hooks/useSortBy' export { useSortBy } from './plugin-hooks/useSortBy'
export { usePagination } from './plugin-hooks/usePagination' 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 { useRowSelect } from './plugin-hooks/useRowSelect'
export { useRowState } from './plugin-hooks/useRowState' export { useRowState } from './plugin-hooks/useRowState'
export { useColumnOrder } from './plugin-hooks/useColumnOrder' export { useColumnOrder } from './plugin-hooks/useColumnOrder'

View File

@ -14,7 +14,7 @@ import { flattenColumns, getFirstDefined } from '../utils'
actions.resetPivot = 'resetPivot' actions.resetPivot = 'resetPivot'
actions.togglePivot = 'togglePivot' actions.togglePivot = 'togglePivot'
export const usePivotColumns = hooks => { export const _UNSTABLE_usePivotColumns = hooks => {
hooks.getPivotToggleProps = [defaultGetPivotToggleProps] hooks.getPivotToggleProps = [defaultGetPivotToggleProps]
hooks.stateReducers.push(reducer) hooks.stateReducers.push(reducer)
hooks.useInstanceAfterData.push(useInstanceAfterData) hooks.useInstanceAfterData.push(useInstanceAfterData)
@ -28,7 +28,7 @@ export const usePivotColumns = hooks => {
hooks.prepareRow.push(prepareRow) hooks.prepareRow.push(prepareRow)
} }
usePivotColumns.pluginName = 'usePivotColumns' _UNSTABLE_usePivotColumns.pluginName = 'usePivotColumns'
const defaultPivotColumns = [] const defaultPivotColumns = []

View File

@ -75,7 +75,7 @@ function App() {
cursor: 'pointer', cursor: 'pointer',
paddingLeft: `${row.depth * 2}rem`, paddingLeft: `${row.depth * 2}rem`,
}} }}
onClick={() => row.toggleExpanded()} onClick={() => row.toggleRowExpanded()}
> >
{row.isExpanded ? 'Collapse' : 'Expand'} Row {row.id} {row.isExpanded ? 'Collapse' : 'Expand'} Row {row.id}
</span> </span>

View File

@ -113,7 +113,7 @@ function Table({ columns, data }) {
style={{ style={{
cursor: 'pointer', cursor: 'pointer',
}} }}
onClick={() => row.toggleExpanded()} onClick={() => row.toggleRowExpanded()}
> >
{row.isExpanded ? '👇' : '👉'} {row.isExpanded ? '👇' : '👉'}
</span> </span>

View File

@ -2,7 +2,6 @@ import React from 'react'
import { render, fireEvent } from '../../../test-utils/react-testing' import { render, fireEvent } from '../../../test-utils/react-testing'
import { useTable } from '../../hooks/useTable' import { useTable } from '../../hooks/useTable'
import { useRowState } from '../useRowState' import { useRowState } from '../useRowState'
import { useGlobalFilter } from '../useGlobalFilter'
const data = [ const data = [
{ {
@ -67,8 +66,7 @@ function Table({ columns, data }) {
initialRowStateAccessor: () => ({ count: 0 }), initialRowStateAccessor: () => ({ count: 0 }),
initialCellStateAccessor: () => ({ count: 0 }), initialCellStateAccessor: () => ({ count: 0 }),
}, },
useRowState, useRowState
useGlobalFilter
) )
return ( return (

View File

@ -5,19 +5,18 @@ import { expandRows } from '../utils'
import { import {
useGetLatest, useGetLatest,
actions, actions,
functionalUpdate,
useMountedLayoutEffect, useMountedLayoutEffect,
makePropGetter, makePropGetter,
} from '../publicUtils' } from '../publicUtils'
// Actions // Actions
actions.toggleExpanded = 'toggleExpanded'
actions.toggleAllExpanded = 'toggleAllExpanded'
actions.setExpanded = 'setExpanded'
actions.resetExpanded = 'resetExpanded' actions.resetExpanded = 'resetExpanded'
actions.toggleRowExpanded = 'toggleRowExpanded'
actions.toggleAllRowsExpanded = 'toggleAllRowsExpanded'
export const useExpanded = hooks => { export const useExpanded = hooks => {
hooks.getExpandedToggleProps = [defaultGetExpandedToggleProps] hooks.getToggleAllRowsExpandedProps = [defaultGetToggleAllRowsExpandedProps]
hooks.getToggleRowExpandedProps = [defaultGetToggleRowExpandedProps]
hooks.stateReducers.push(reducer) hooks.stateReducers.push(reducer)
hooks.useInstance.push(useInstance) hooks.useInstance.push(useInstance)
hooks.prepareRow.push(prepareRow) hooks.prepareRow.push(prepareRow)
@ -25,17 +24,29 @@ export const useExpanded = hooks => {
useExpanded.pluginName = 'useExpanded' useExpanded.pluginName = 'useExpanded'
const defaultGetExpandedToggleProps = (props, { row }) => [ const defaultGetToggleAllRowsExpandedProps = (props, { instance }) => [
props, props,
{ {
onClick: e => { onClick: e => {
e.persist() instance.toggleAllRowsExpanded()
row.toggleExpanded()
}, },
style: { style: {
cursor: 'pointer', 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 { return {
...state, ...state,
expanded: functionalUpdate(action.expanded, state.expanded), expanded: {},
} }
} }
if (action.type === actions.toggleExpanded) { if (action.type === actions.toggleRowExpanded) {
const { id, value: setExpanded } = action const { id, value: setExpanded } = action
const exists = state.expanded[id] const exists = state.expanded[id]
@ -93,16 +122,28 @@ function useInstance(instance) {
const { const {
data, data,
rows, rows,
rowsById,
manualExpandedKey = 'expanded', manualExpandedKey = 'expanded',
paginateExpandedRows = true, paginateExpandedRows = true,
expandSubRows = true, expandSubRows = true,
autoResetExpanded = true, autoResetExpanded = true,
getHooks,
state: { expanded }, state: { expanded },
dispatch, dispatch,
} = instance } = instance
const getAutoResetExpanded = useGetLatest(autoResetExpanded) 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 // Bypass any effects from firing when this changes
useMountedLayoutEffect(() => { useMountedLayoutEffect(() => {
if (getAutoResetExpanded()) { if (getAutoResetExpanded()) {
@ -110,13 +151,18 @@ function useInstance(instance) {
} }
}, [dispatch, data]) }, [dispatch, data])
const toggleExpanded = React.useCallback( const toggleRowExpanded = React.useCallback(
(id, value) => { (id, value) => {
dispatch({ type: actions.toggleExpanded, id, value }) dispatch({ type: actions.toggleRowExpanded, id, value })
}, },
[dispatch] [dispatch]
) )
const toggleAllRowsExpanded = React.useCallback(
value => dispatch({ type: actions.toggleAllRowsExpanded, value }),
[dispatch]
)
const expandedRows = React.useMemo(() => { const expandedRows = React.useMemo(() => {
if (paginateExpandedRows) { if (paginateExpandedRows) {
return expandRows(rows, { manualExpandedKey, expanded, expandSubRows }) return expandRows(rows, { manualExpandedKey, expanded, expandSubRows })
@ -129,20 +175,30 @@ function useInstance(instance) {
expanded, expanded,
]) ])
const getInstance = useGetLatest(instance)
const getToggleAllRowsExpandedProps = makePropGetter(
getHooks().getToggleAllRowsExpandedProps,
{ instance: getInstance() }
)
Object.assign(instance, { Object.assign(instance, {
preExpandedRows: rows, preExpandedRows: rows,
expandedRows, expandedRows,
rows: expandedRows, rows: expandedRows,
toggleExpanded,
expandedDepth, expandedDepth,
isAllRowsExpanded,
toggleRowExpanded,
toggleAllRowsExpanded,
getToggleAllRowsExpandedProps,
}) })
} }
function prepareRow(row, { instance: { getHooks }, instance }) { 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( row.getToggleRowExpandedProps = makePropGetter(
getHooks().getExpandedToggleProps, getHooks().getToggleRowExpandedProps,
{ {
instance, instance,
row, row,

View File

@ -108,7 +108,9 @@ function reducer(state, action, previousState, instance) {
filterTypes filterTypes
) )
if (shouldAutoRemoveFilter(filterMethod.autoRemove, filter.value, column)) { if (
shouldAutoRemoveFilter(filterMethod.autoRemove, filter.value, column)
) {
return false return false
} }
return true return true
@ -122,6 +124,7 @@ function useInstance(instance) {
data, data,
rows, rows,
flatRows, flatRows,
rowsById,
allColumns, allColumns,
filterTypes: userFilterTypes, filterTypes: userFilterTypes,
manualFilters, manualFilters,
@ -175,12 +178,17 @@ function useInstance(instance) {
column.filterValue = found && found.value column.filterValue = found && found.value
}) })
const [filteredRows, filteredFlatRows] = React.useMemo(() => { const [
filteredRows,
filteredFlatRows,
filteredRowsById,
] = React.useMemo(() => {
if (manualFilters || !filters.length) { if (manualFilters || !filters.length) {
return [rows, flatRows] return [rows, flatRows, rowsById]
} }
const filteredFlatRows = [] const filteredFlatRows = []
const filteredRowsById = {}
// Filters top level and nested rows // Filters top level and nested rows
const filterRows = (rows, depth = 0) => { const filterRows = (rows, depth = 0) => {
@ -231,6 +239,7 @@ function useInstance(instance) {
// would be required to do that recursion in some scenarios // would be required to do that recursion in some scenarios
filteredRows = filteredRows.map(row => { filteredRows = filteredRows.map(row => {
filteredFlatRows.push(row) filteredFlatRows.push(row)
filteredRowsById[row.id] = row
if (!row.subRows) { if (!row.subRows) {
return row return row
} }
@ -246,8 +255,16 @@ function useInstance(instance) {
return filteredRows return filteredRows
} }
return [filterRows(rows), filteredFlatRows] return [filterRows(rows), filteredFlatRows, filteredRowsById]
}, [manualFilters, filters, rows, flatRows, allColumns, userFilterTypes]) }, [
manualFilters,
filters,
rows,
flatRows,
rowsById,
allColumns,
userFilterTypes,
])
React.useMemo(() => { React.useMemo(() => {
// Now that each filtered column has it's partially filtered rows, // Now that each filtered column has it's partially filtered rows,
@ -275,10 +292,13 @@ function useInstance(instance) {
Object.assign(instance, { Object.assign(instance, {
preFilteredRows: rows, preFilteredRows: rows,
preFilteredFlatRows: flatRows, preFilteredFlatRows: flatRows,
preFilteredRowsById: rowsById,
filteredRows, filteredRows,
filteredFlatRows, filteredFlatRows,
filteredRowsById,
rows: filteredRows, rows: filteredRows,
flatRows: filteredFlatRows, flatRows: filteredFlatRows,
rowsById: filteredRowsById,
setFilter, setFilter,
setAllFilters, setAllFilters,
}) })

View File

@ -61,6 +61,7 @@ function useInstance(instance) {
data, data,
rows, rows,
flatRows, flatRows,
rowsById,
allColumns, allColumns,
filterTypes: userFilterTypes, filterTypes: userFilterTypes,
globalFilter, globalFilter,
@ -88,12 +89,17 @@ function useInstance(instance) {
// cache for each row group (top-level rows, and each row's recursive subrows) // 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? // 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') { if (manualGlobalFilter || typeof globalFilterValue === 'undefined') {
return [rows, flatRows] return [rows, flatRows, rowsById]
} }
const filteredFlatRows = [] const filteredFlatRows = []
const filteredRowsById = {}
const filterMethod = getFilterMethod( const filterMethod = getFilterMethod(
globalFilter, globalFilter,
@ -114,6 +120,7 @@ function useInstance(instance) {
globalFilterValue globalFilterValue
).map(row => { ).map(row => {
filteredFlatRows.push(row) filteredFlatRows.push(row)
filteredRowsById[row.id] = row
return { return {
...row, ...row,
@ -125,15 +132,16 @@ function useInstance(instance) {
}) })
} }
return [filterRows(rows), filteredFlatRows] return [filterRows(rows), filteredFlatRows, filteredRowsById]
}, [ }, [
manualGlobalFilter, manualGlobalFilter,
globalFilterValue,
globalFilter, globalFilter,
userFilterTypes, userFilterTypes,
rows, rows,
flatRows, flatRows,
rowsById,
allColumns, allColumns,
globalFilterValue,
]) ])
const getAutoResetGlobalFilter = useGetLatest(autoResetGlobalFilter) const getAutoResetGlobalFilter = useGetLatest(autoResetGlobalFilter)
@ -147,10 +155,13 @@ function useInstance(instance) {
Object.assign(instance, { Object.assign(instance, {
preGlobalFilteredRows: rows, preGlobalFilteredRows: rows,
preGlobalFilteredFlatRows: flatRows, preGlobalFilteredFlatRows: flatRows,
preGlobalFilteredRowsById: rowsById,
globalFilteredRows, globalFilteredRows,
globalFilteredFlatRows, globalFilteredFlatRows,
globalFilteredRowsById,
rows: globalFilteredRows, rows: globalFilteredRows,
flatRows: globalFilteredFlatRows, flatRows: globalFilteredFlatRows,
rowsById: globalFilteredRowsById,
setGlobalFilter, setGlobalFilter,
}) })
} }

View File

@ -13,6 +13,9 @@ import {
useGetLatest, useGetLatest,
} from '../publicUtils' } from '../publicUtils'
const emptyArray = []
const emptyObject = {}
// Actions // Actions
actions.resetGroupBy = 'resetGroupBy' actions.resetGroupBy = 'resetGroupBy'
actions.toggleGroupBy = 'toggleGroupBy' actions.toggleGroupBy = 'toggleGroupBy'
@ -119,6 +122,7 @@ function useInstance(instance) {
data, data,
rows, rows,
flatRows, flatRows,
rowsById,
allColumns, allColumns,
flatHeaders, flatHeaders,
groupByFn = defaultGroupByFn, 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) { if (manualGroupBy || !groupBy.length) {
return [rows, flatRows] return [
rows,
flatRows,
rowsById,
emptyArray,
emptyObject,
flatRows,
rowsById,
]
} }
// Ensure that the list of filtered columns exist // Ensure that the list of filtered columns exist
@ -252,6 +272,11 @@ function useInstance(instance) {
} }
let groupedFlatRows = [] let groupedFlatRows = []
const groupedRowsById = {}
const onlyGroupedFlatRows = []
const onlyGroupedRowsById = {}
const nonGroupedFlatRows = []
const nonGroupedRowsById = {}
// Recursively group the data // Recursively group the data
const groupUpRecursively = (rows, depth = 0, parentId) => { const groupUpRecursively = (rows, depth = 0, parentId) => {
@ -293,7 +318,17 @@ function useInstance(instance) {
index, 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 return row
} }
@ -304,13 +339,34 @@ function useInstance(instance) {
const groupedRows = groupUpRecursively(rows) 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 // Assign the new data
return [groupedRows, groupedFlatRows] return [
groupedRows,
groupedFlatRows,
groupedRowsById,
onlyGroupedFlatRows,
onlyGroupedRowsById,
nonGroupedFlatRows,
nonGroupedRowsById,
]
}, [ }, [
manualGroupBy, manualGroupBy,
groupBy, groupBy,
rows, rows,
flatRows, flatRows,
rowsById,
allColumns, allColumns,
userAggregations, userAggregations,
groupByFn, groupByFn,
@ -327,10 +383,17 @@ function useInstance(instance) {
Object.assign(instance, { Object.assign(instance, {
preGroupedRows: rows, preGroupedRows: rows,
preGroupedFlatRow: flatRows, preGroupedFlatRow: flatRows,
preGroupedRowsById: rowsById,
groupedRows, groupedRows,
groupedFlatRows, groupedFlatRows,
groupedRowsById,
onlyGroupedFlatRows,
onlyGroupedRowsById,
nonGroupedFlatRows,
nonGroupedRowsById,
rows: groupedRows, rows: groupedRows,
flatRows: groupedFlatRows, flatRows: groupedFlatRows,
rowsById: groupedRowsById,
toggleGroupBy, toggleGroupBy,
}) })
} }

View File

@ -77,7 +77,15 @@ function useInstance(instance) {
pageCount: userPageCount, pageCount: userPageCount,
paginateExpandedRows = true, paginateExpandedRows = true,
expandSubRows = true, expandSubRows = true,
state: { pageSize, pageIndex, expanded, globalFilter, filters, groupBy, sortBy }, state: {
pageSize,
pageIndex,
expanded,
globalFilter,
filters,
groupBy,
sortBy,
},
dispatch, dispatch,
data, data,
manualPagination, manualPagination,
@ -103,10 +111,10 @@ function useInstance(instance) {
}, [ }, [
dispatch, dispatch,
manualPagination ? null : data, manualPagination ? null : data,
manualPagination || manualGlobalFilter ? null : globalFilter, manualGlobalFilter ? null : globalFilter,
manualPagination || manualFilters ? null : filters, manualFilters ? null : filters,
manualPagination || manualGroupBy ? null : groupBy, manualGroupBy ? null : groupBy,
manualPagination || manualSortBy ? null : sortBy, manualSortBy ? null : sortBy,
]) ])
const pageCount = manualPagination const pageCount = manualPagination

View File

@ -86,7 +86,11 @@ function reducer(state, action, previousState, instance) {
if (action.type === actions.toggleAllRowsSelected) { if (action.type === actions.toggleAllRowsSelected) {
const { value: setSelected } = action const { value: setSelected } = action
const { isAllRowsSelected, flatRowsById } = instance const {
isAllRowsSelected,
rowsById,
nonGroupedRowsById = rowsById,
} = instance
const selectAll = const selectAll =
typeof setSelected !== 'undefined' ? setSelected : !isAllRowsSelected typeof setSelected !== 'undefined' ? setSelected : !isAllRowsSelected
@ -94,7 +98,7 @@ function reducer(state, action, previousState, instance) {
if (selectAll) { if (selectAll) {
const selectedRowIds = {} const selectedRowIds = {}
Object.keys(flatRowsById).forEach(rowId => { Object.keys(nonGroupedRowsById).forEach(rowId => {
selectedRowIds[rowId] = true selectedRowIds[rowId] = true
}) })
@ -112,12 +116,12 @@ function reducer(state, action, previousState, instance) {
if (action.type === actions.toggleRowSelected) { if (action.type === actions.toggleRowSelected) {
const { id, value: setSelected } = action const { id, value: setSelected } = action
const { flatGroupedRowsById, selectSubRows = true } = instance const { rowsById, selectSubRows = true } = instance
// Join the ids of deep rows // Join the ids of deep rows
// to make a key, then manage all of the keys // to make a key, then manage all of the keys
// in a flat object // in a flat object
const row = flatGroupedRowsById[id] const row = rowsById[id]
const isSelected = row.isSelected const isSelected = row.isSelected
const shouldExist = const shouldExist =
typeof setSelected !== 'undefined' ? setSelected : !isSelected typeof setSelected !== 'undefined' ? setSelected : !isSelected
@ -129,7 +133,7 @@ function reducer(state, action, previousState, instance) {
let newSelectedRowIds = { ...state.selectedRowIds } let newSelectedRowIds = { ...state.selectedRowIds }
const handleRowById = id => { const handleRowById = id => {
const row = flatGroupedRowsById[id] const row = rowsById[id]
if (!row.isGrouped) { if (!row.isGrouped) {
if (!isSelected && shouldExist) { if (!isSelected && shouldExist) {
@ -159,7 +163,8 @@ function useInstance(instance) {
rows, rows,
getHooks, getHooks,
plugins, plugins,
flatRows, rowsById,
nonGroupedRowsById = rowsById,
autoResetSelectedRows = true, autoResetSelectedRows = true,
state: { selectedRowIds }, state: { selectedRowIds },
selectSubRows = true, 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 = React.useMemo(() => {
const selectedFlatRows = [] const selectedFlatRows = []
@ -206,11 +197,11 @@ function useInstance(instance) {
}, [rows, selectSubRows, selectedRowIds]) }, [rows, selectSubRows, selectedRowIds])
let isAllRowsSelected = Boolean( let isAllRowsSelected = Boolean(
Object.keys(flatRowsById).length && Object.keys(selectedRowIds).length Object.keys(nonGroupedRowsById).length && Object.keys(selectedRowIds).length
) )
if (isAllRowsSelected) { if (isAllRowsSelected) {
if (Object.keys(flatRowsById).some(id => !selectedRowIds[id])) { if (Object.keys(nonGroupedRowsById).some(id => !selectedRowIds[id])) {
isAllRowsSelected = false isAllRowsSelected = false
} }
} }
@ -229,8 +220,7 @@ function useInstance(instance) {
) )
const toggleRowSelected = React.useCallback( const toggleRowSelected = React.useCallback(
(id, value) => (id, value) => dispatch({ type: actions.toggleRowSelected, id, value }),
dispatch({ type: actions.toggleRowSelected, id, value }),
[dispatch] [dispatch]
) )
@ -242,13 +232,11 @@ function useInstance(instance) {
) )
Object.assign(instance, { Object.assign(instance, {
flatRowsById,
flatGroupedRowsById,
selectedFlatRows, selectedFlatRows,
isAllRowsSelected,
toggleRowSelected, toggleRowSelected,
toggleAllRowsSelected, toggleAllRowsSelected,
getToggleAllRowsSelectedProps, getToggleAllRowsSelectedProps,
isAllRowsSelected,
}) })
} }

View File

@ -180,6 +180,7 @@ function useInstance(instance) {
const { const {
data, data,
rows, rows,
flatRows,
allColumns, allColumns,
orderByFn = defaultOrderByFn, orderByFn = defaultOrderByFn,
sortTypes: userSortTypes, sortTypes: userSortTypes,
@ -194,7 +195,7 @@ function useInstance(instance) {
autoResetSortBy = true, autoResetSortBy = true,
} = instance } = instance
ensurePluginOrder(plugins, ['useFilters'], 'useSortBy', []) ensurePluginOrder(plugins, ['useFilters'], 'useSortBy', ['useExpanded'])
// Updates sorting based on a columnId, desc flag and multi flag // Updates sorting based on a columnId, desc flag and multi flag
const toggleSortBy = React.useCallback( const toggleSortBy = React.useCallback(
@ -249,11 +250,13 @@ function useInstance(instance) {
column.isSortedDesc = column.isSorted ? columnSort.desc : undefined column.isSortedDesc = column.isSorted ? columnSort.desc : undefined
}) })
const sortedRows = React.useMemo(() => { const [sortedRows, sortedFlatRows] = React.useMemo(() => {
if (manualSortBy || !sortBy.length) { if (manualSortBy || !sortBy.length) {
return rows return [rows, flatRows]
} }
const sortedFlatRows = []
// Filter out sortBys that correspond to non existing columns // Filter out sortBys that correspond to non existing columns
const availableSortBy = sortBy.filter(sort => const availableSortBy = sortBy.filter(sort =>
allColumns.find(col => col.id === sort.id) allColumns.find(col => col.id === sort.id)
@ -314,6 +317,7 @@ function useInstance(instance) {
// If there are sub-rows, sort them // If there are sub-rows, sort them
sortedData.forEach(row => { sortedData.forEach(row => {
sortedFlatRows.push(row)
if (!row.subRows || row.subRows.length <= 1) { if (!row.subRows || row.subRows.length <= 1) {
return return
} }
@ -323,8 +327,16 @@ function useInstance(instance) {
return sortedData return sortedData
} }
return sortData(rows) return [sortData(rows), sortedFlatRows]
}, [manualSortBy, sortBy, rows, allColumns, orderByFn, userSortTypes]) }, [
manualSortBy,
sortBy,
rows,
flatRows,
allColumns,
orderByFn,
userSortTypes,
])
const getAutoResetSortBy = useGetLatest(autoResetSortBy) const getAutoResetSortBy = useGetLatest(autoResetSortBy)
@ -336,8 +348,11 @@ function useInstance(instance) {
Object.assign(instance, { Object.assign(instance, {
preSortedRows: rows, preSortedRows: rows,
preSortedFlatRows: flatRows,
sortedRows, sortedRows,
sortedFlatRows,
rows: sortedRows, rows: sortedRows,
flatRows: sortedFlatRows,
toggleSortBy, toggleSortBy,
}) })
} }

View File

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import { defaultColumn, reduceHooks } from './publicUtils' import { defaultColumn } from './publicUtils'
// Find the depth of the columns // Find the depth of the columns
export function findMaxDepth(columns, depth = 0) { export function findMaxDepth(columns, depth = 0) {
@ -88,97 +88,6 @@ export function decorateColumn(column, userDefaultColumn) {
return column 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 // Build the header groups from the bottom up
export function makeHeaderGroups(allColumns, defaultColumn) { export function makeHeaderGroups(allColumns, defaultColumn) {
const headerGroups = [] const headerGroups = []