diff --git a/README.md b/README.md
index 81c0037..29b578e 100644
--- a/README.md
+++ b/README.md
@@ -33,9 +33,7 @@ Hooks for building **lightweight, fast and extendable datagrids** for React
- Extensible via hooks
- "Why I wrote React Table and the problems it has solved for Nozzle.io" by Tanner Linsley
-## Demos
-
-[React Table v7 Sandbox](https://codesandbox.io/s/m5lxzzpz69)
+## [See Examples](#examples)
## Versions
@@ -237,12 +235,15 @@ const instance = useTable(
1. `useTable` is called. A table instance is created.
1. The `instance.state` is resolved from either a custom user state or an automatically generated one.
-1. A collection of plugin points is created at `instance.hooks`. These plugin points don't run until after all of the plugins have run.
-1. The instance is reduced through each plugin hook in the order they were called. Each hook receives the result of the previous hook, is able to manipulate the `instance`, use plugin points, use their own React hooks internally and eventually return a new `instance`. This happens until the last instance object is returned from the last hook.
-1. Lastly, the plugin points that were registered and populated during hook reduction are run to produce the final instance object that is returned from `useTable`
+1. A collection of plugin points is created at `instance.hooks`.
+1. Each plugin is given the opportunity to add hooks to `instance.hook`.
+1. As the `useTable` logic proceeds to run, each plugin hook type is used at a specific point in time with each individual hook function being executed the order it was registered.
+1. The final instance object is returned from `useTable`, which the developer then uses to construct their table.
This multi-stage process is the secret sauce that allows React Table plugin hooks to work together and compose nicely, while not stepping on each others toes.
+To dive deeper into plugins, see [Plugins](TODO) and the [Plugin Guide](TODO)
+
### Plugin Hook Order & Consistency
The order and usage of plugin hooks must follow [The Laws of Hooks](TODO), just like any other custom hook. They must always be unconditionally called in the same order.
@@ -286,9 +287,6 @@ The following options are supported via the main options object passed to `useTa
- The default column object for every column passed to React Table.
- Column-specific properties will override the properties in this object, eg. `{ ...defaultColumn, ...userColumn }`
- This is particularly useful for adding global column properties. For instance, when using the `useFilters` plugin hook, add a default `Filter` renderer for every column, eg.`{ Filter: MyDefaultFilterComponent }`
-- `useRows: Function`
- - Optional
- - This hook overrides the internal `useRows` hooks used by `useTable`. You probably never want to override this unless you are testing or developing new features for React Table
- `debug: Bool`
- Optional
- A flag to turn on debug mode.
@@ -580,7 +578,7 @@ The following values are provided to the table `instance`:
The following properties are available on every `Column` object returned by the table instance.
-- `canSortBy: Bool`
+- `canSort: Bool`
- Denotes whether a column is sortable or not depending on if it has a valid accessor/data model or is manually disabled via an option.
- `toggleSortBy: Function(descending, multi) => void`
- This function can be used to programmatically toggle the sorting for this column.
diff --git a/examples/sorting-client-side/src/App.js b/examples/sorting-client-side/src/App.js
index 12d50d1..9701d30 100644
--- a/examples/sorting-client-side/src/App.js
+++ b/examples/sorting-client-side/src/App.js
@@ -33,47 +33,72 @@ const Styles = styled.div`
}
`
+const defaultColumn = {
+ sort: 'numeric',
+}
+
function Table({ columns, data }) {
const { getTableProps, headerGroups, rows, prepareRow } = useTable(
{
columns,
data,
+ defaultColumn,
+ debug: true,
},
useSortBy
)
+ // We don't want to render all 2000 rows for this example, so cap
+ // it at 20 for this use case
+ const firstPageRows = rows.slice(0, 20)
+
return (
-
-
- {headerGroups.map(headerGroup => (
-
- {headerGroup.headers.map(column => (
- // Add the sorting props to control sorting. For this example
- // we can add them into the header props
- |
- {column.render('Header')}
- {/* Add a sort direction indicator */}
-
- {column.sorted ? (column.sortedDesc ? ' 🔽' : ' 🔼') : ''}
-
- |
- ))}
-
- ))}
-
-
- {rows.map(
- (row, i) =>
- prepareRow(row) || (
-
- {row.cells.map(cell => {
- return | {cell.render('Cell')} |
- })}
-
- )
- )}
-
-
+ <>
+
+
+ {headerGroups.map(headerGroup => (
+
+ {headerGroup.headers.map(
+ column =>
+ console.log(column) || (
+ // Add the sorting props to control sorting. For this example
+ // we can add them into the header props
+ |
+ {column.render('Header')}
+ {/* Add a sort direction indicator */}
+
+ {column.sorted
+ ? column.sortedDesc
+ ? ' 🔽'
+ : ' 🔼'
+ : ''}
+
+ |
+ )
+ )}
+
+ ))}
+
+
+ {firstPageRows.map(
+ (row, i) =>
+ prepareRow(row) || (
+
+ {row.cells.map(cell => {
+ return (
+ | {cell.render('Cell')} |
+ )
+ })}
+
+ )
+ )}
+
+
+
+ Showing the first 20 results of {rows.length} rows
+ >
)
}
@@ -118,7 +143,7 @@ function App() {
[]
)
- const data = React.useMemo(() => makeData(20), [])
+ const data = React.useMemo(() => makeData(1000), [])
return (
diff --git a/src/filterTypes.js b/src/filterTypes.js
index ec9b371..cfa1ec2 100644
--- a/src/filterTypes.js
+++ b/src/filterTypes.js
@@ -89,6 +89,4 @@ export const between = (rows, id, filterValue) => {
}
between.autoRemove = val =>
- console.log(val) ||
- !val ||
- (typeof val[0] !== 'number' && typeof val[1] !== 'number')
+ !val || (typeof val[0] !== 'number' && typeof val[1] !== 'number')
diff --git a/src/hooks/tests/useTable.test.js b/src/hooks/tests/useTable.test.js
new file mode 100644
index 0000000..96c1f94
--- /dev/null
+++ b/src/hooks/tests/useTable.test.js
@@ -0,0 +1,111 @@
+import '@testing-library/react/cleanup-after-each'
+import '@testing-library/jest-dom/extend-expect'
+// NOTE: jest-dom adds handy assertions to Jest and is recommended, but not required
+
+import React from 'react'
+import { render } from '@testing-library/react'
+import useTable from './useTable'
+
+function Table({ columns, data }) {
+ // Use the state and functions returned from useTable to build your UI
+ const { getTableProps, headerGroups, rows, prepareRow } = useTable({
+ columns,
+ data,
+ })
+
+ // Render the UI for your table
+ return (
+
+
+ {headerGroups.map(headerGroup => (
+
+ {headerGroup.headers.map(column => (
+ | {column.render('Header')} |
+ ))}
+
+ ))}
+
+
+ {rows.map(
+ (row, i) =>
+ prepareRow(row) || (
+
+ {row.cells.map(cell => {
+ return | {cell.render('Cell')} |
+ })}
+
+ )
+ )}
+
+
+ )
+}
+
+function App() {
+ const columns = React.useMemo(
+ () => [
+ {
+ Header: 'Name',
+ columns: [
+ {
+ Header: 'First Name',
+ accessor: 'firstName',
+ },
+ {
+ Header: 'Last Name',
+ accessor: 'lastName',
+ },
+ ],
+ },
+ {
+ Header: 'Info',
+ columns: [
+ {
+ Header: 'Age',
+ accessor: 'age',
+ },
+ {
+ Header: 'Visits',
+ accessor: 'visits',
+ },
+ {
+ Header: 'Status',
+ accessor: 'status',
+ },
+ {
+ Header: 'Profile Progress',
+ accessor: 'progress',
+ },
+ ],
+ },
+ ],
+ []
+ )
+
+ const data = React.useMemo(
+ () => [
+ {
+ firstName: 'tanner',
+ lastName: 'linsley',
+ age: 29,
+ visits: 100,
+ status: 'In Relationship',
+ progress: 50,
+ },
+ ],
+ []
+ )
+
+ return
+}
+
+test('renders a basic table', () => {
+ const { getByText } = render()
+
+ expect(getByText('tanner')).toBeInTheDocument()
+ expect(getByText('linsley')).toBeInTheDocument()
+ expect(getByText('29')).toBeInTheDocument()
+ expect(getByText('100')).toBeInTheDocument()
+ expect(getByText('In Relationship')).toBeInTheDocument()
+ expect(getByText('50')).toBeInTheDocument()
+})
diff --git a/src/hooks/useTable.js b/src/hooks/useTable.js
index 028d064..a54aa0d 100755
--- a/src/hooks/useTable.js
+++ b/src/hooks/useTable.js
@@ -9,6 +9,7 @@ import {
decorateColumnTree,
makeHeaderGroups,
findMaxDepth,
+ flattenBy,
} from '../utils'
import { useTableState } from './useTableState'
@@ -52,34 +53,36 @@ export const useTable = (props, ...plugins) => {
// But use the users state if provided
const state = userState || defaultState
- // These are hooks that plugins can use right before render
- const hooks = {
- beforeRender: [],
- columns: [],
- headers: [],
- headerGroups: [],
- rows: [],
- row: [],
- getTableProps: [],
- getRowProps: [],
- getHeaderGroupProps: [],
- getHeaderProps: [],
- getCellProps: [],
- }
-
// The initial api
- let api = {
+ let instanceRef = React.useRef({})
+
+ Object.assign(instanceRef.current, {
...props,
data,
state,
- hooks,
plugins,
- }
+ hooks: {
+ columnsBeforeHeaderGroups: [],
+ useMain: [],
+ useColumns: [],
+ useHeaders: [],
+ useHeaderGroups: [],
+ useRows: [],
+ prepareRow: [],
+ getTableProps: [],
+ getRowProps: [],
+ getHeaderGroupProps: [],
+ getHeaderProps: [],
+ getCellProps: [],
+ },
+ })
- if (debug) console.time('hooks')
- // Loop through plugins to build the api out
- api = plugins.filter(Boolean).reduce((prev, next) => next(prev), api)
- if (debug) console.timeEnd('hooks')
+ // Allow plugins to register hooks
+ if (debug) console.time('plugins')
+ plugins.filter(Boolean).forEach(plugin => {
+ plugin(instanceRef.current.hooks)
+ })
+ if (debug) console.timeEnd('plugins')
// Compute columns, headerGroups and headers
const columnInfo = React.useMemo(
@@ -93,7 +96,11 @@ export const useTable = (props, ...plugins) => {
// Allow hooks to decorate columns
if (debug) console.time('hooks.columnsBeforeHeaderGroups')
- columns = applyHooks(api.hooks.columnsBeforeHeaderGroups, columns, api)
+ columns = applyHooks(
+ instanceRef.current.hooks.columnsBeforeHeaderGroups,
+ columns,
+ instanceRef.current
+ )
if (debug) console.timeEnd('hooks.columnsBeforeHeaderGroups')
// Make the headerGroups
@@ -111,17 +118,16 @@ export const useTable = (props, ...plugins) => {
headers,
}
},
- [api, debug, defaultColumn, userColumns]
+ [debug, defaultColumn, userColumns]
)
// Place the columns, headerGroups and headers on the api
- Object.assign(api, columnInfo)
+ Object.assign(instanceRef.current, columnInfo)
// Access the row model
- api.rows = React.useMemo(
+ instanceRef.current.rows = React.useMemo(
() => {
- if (debug) console.info('getAccessedRows')
-
+ if (debug) console.time('getAccessedRows')
// Access the row's data
const accessRow = (originalRow, i, depth = 0) => {
// Keep the original reference around
@@ -155,7 +161,7 @@ export const useTable = (props, ...plugins) => {
// Create the cells and values
row.values = {}
- api.columns.forEach(column => {
+ instanceRef.current.columns.forEach(column => {
row.values[column.id] = column.accessor
? column.accessor(originalRow, i, { subRows, depth, data })
: undefined
@@ -165,61 +171,89 @@ export const useTable = (props, ...plugins) => {
}
// Use the resolved data
- return data.map((d, i) => accessRow(d, i))
+ const accessedData = data.map((d, i) => accessRow(d, i))
+ if (debug) console.timeEnd('getAccessedRows')
+ return accessedData
},
- [debug, data, subRowsKey, api.columns]
+ [debug, data, subRowsKey]
)
// Determine column visibility
- api.columns.forEach(column => {
+ instanceRef.current.columns.forEach(column => {
column.visible =
- typeof column.show === 'function' ? column.show(api) : !!column.show
+ typeof column.show === 'function'
+ ? column.show(instanceRef.current)
+ : !!column.show
})
- // Allow hooks to decorate columns
- if (debug) console.time('hooks.columns')
- api.columns = applyHooks(api.hooks.columns, api.columns, api)
- if (debug) console.timeEnd('hooks.columns')
+ if (debug) console.time('hooks.useMain')
+ instanceRef.current = applyHooks(
+ instanceRef.current.hooks.useMain,
+ instanceRef.current
+ )
+ if (debug)
+ console.timeEnd('hooks.useMain')
- // Allow hooks to decorate headers
- if (debug) console.time('hooks.headers')
- api.headers = applyHooks(api.hooks.headers, api.headers, api)
- if (debug) console.timeEnd('hooks.headers')
- ;[...api.columns, ...api.headers].forEach(column => {
- // Give columns/headers rendering power
- column.render = (type, userProps = {}) => {
- const Comp = typeof type === 'string' ? column[type] : type
+ // // Allow hooks to decorate columns
+ // if (debug) console.time('hooks.useColumns')
+ // instanceRef.current.columns = applyHooks(
+ // instanceRef.current.hooks.useColumns,
+ // instanceRef.current.columns,
+ // instanceRef.current
+ // )
+ // if (debug) console.timeEnd('hooks.useColumns')
- if (typeof Comp === 'undefined') {
- throw new Error(renderErr)
+ // // Allow hooks to decorate headers
+ // if (debug) console.time('hooks.useHeaders')
+ // instanceRef.current.headers = applyHooks(
+ // instanceRef.current.hooks.useHeaders,
+ // instanceRef.current.headers,
+ // instanceRef.current
+ // )
+ // if (debug) console.timeEnd('hooks.useHeaders')
+ ;[...instanceRef.current.columns, ...instanceRef.current.headers].forEach(
+ column => {
+ // Give columns/headers rendering power
+ column.render = (type, userProps = {}) => {
+ const Comp = typeof type === 'string' ? column[type] : type
+
+ if (typeof Comp === 'undefined') {
+ throw new Error(renderErr)
+ }
+
+ return flexRender(Comp, {
+ ...instanceRef.current,
+ ...column,
+ ...userProps,
+ })
}
- return flexRender(Comp, {
- ...api,
- ...column,
- ...userProps,
- })
+ // Give columns/headers a default getHeaderProps
+ column.getHeaderProps = props =>
+ mergeProps(
+ {
+ key: ['header', column.id].join('_'),
+ colSpan: column.columns ? column.columns.length : 1,
+ },
+ applyPropHooks(
+ instanceRef.current.hooks.getHeaderProps,
+ column,
+ instanceRef.current
+ ),
+ props
+ )
}
+ )
- // Give columns/headers a default getHeaderProps
- column.getHeaderProps = props =>
- mergeProps(
- {
- key: ['header', column.id].join('_'),
- colSpan: column.columns ? column.columns.length : 1,
- },
- applyPropHooks(api.hooks.getHeaderProps, column, api),
- props
- )
- })
+ // // Allow hooks to decorate headerGroups
+ // if (debug) console.time('hooks.useHeaderGroups')
+ // instanceRef.current.headerGroups = applyHooks(
+ // instanceRef.current.hooks.useHeaderGroups,
+ // instanceRef.current.headerGroups,
+ // instanceRef.current
+ // )
- // Allow hooks to decorate headerGroups
- if (debug) console.time('hooks.headerGroups')
- api.headerGroups = applyHooks(
- api.hooks.headerGroups,
- api.headerGroups,
- api
- ).filter((headerGroup, i) => {
+ instanceRef.current.headerGroups.filter((headerGroup, i) => {
// Filter out any headers and headerGroups that don't have visible columns
headerGroup.headers = headerGroup.headers.filter(header => {
const recurse = columns =>
@@ -242,7 +276,11 @@ export const useTable = (props, ...plugins) => {
{
key: [`header${i}`].join('_'),
},
- applyPropHooks(api.hooks.getHeaderGroupProps, headerGroup, api),
+ applyPropHooks(
+ instanceRef.current.hooks.getHeaderGroupProps,
+ headerGroup,
+ instanceRef.current
+ ),
props
)
return true
@@ -250,29 +288,39 @@ export const useTable = (props, ...plugins) => {
return false
})
- if (debug) console.timeEnd('hooks.headerGroups')
+ // if (debug) console.timeEnd('hooks.useHeaderGroups')
// Run the rows (this could be a dangerous hook with a ton of data)
- if (debug) console.time('hooks.rows')
- api.rows = applyHooks(api.hooks.rows, api.rows, api)
- if (debug) console.timeEnd('hooks.rows')
+ if (debug) console.time('hooks.useRows')
+ instanceRef.current.rows = applyHooks(
+ instanceRef.current.hooks.useRows,
+ instanceRef.current.rows,
+ instanceRef.current
+ )
+ if (debug) console.timeEnd('hooks.useRows')
// The prepareRow function is absolutely necessary and MUST be called on
// any rows the user wishes to be displayed.
- api.prepareRow = row => {
+ instanceRef.current.prepareRow = row => {
const { path } = row
row.getRowProps = props =>
mergeProps(
{ key: ['row', ...path].join('_') },
- applyPropHooks(api.hooks.getRowProps, row, api),
+ applyPropHooks(
+ instanceRef.current.hooks.getRowProps,
+ row,
+ instanceRef.current
+ ),
props
)
// need to apply any row specific hooks (useExpanded requires this)
- applyHooks(api.hooks.row, row, api)
+ applyHooks(instanceRef.current.hooks.prepareRow, row, instanceRef.current)
- const visibleColumns = api.columns.filter(column => column.visible)
+ const visibleColumns = instanceRef.current.columns.filter(
+ column => column.visible
+ )
// Build the cells for each row
row.cells = visibleColumns.map(column => {
@@ -289,7 +337,11 @@ export const useTable = (props, ...plugins) => {
{
key: ['cell', columnPathStr].join('_'),
},
- applyPropHooks(api.hooks.getCellProps, cell, api),
+ applyPropHooks(
+ instanceRef.current.hooks.getCellProps,
+ cell,
+ instanceRef.current
+ ),
props
)
}
@@ -303,7 +355,7 @@ export const useTable = (props, ...plugins) => {
}
return flexRender(Comp, {
- ...api,
+ ...instanceRef.current,
...cell,
...userProps,
})
@@ -313,26 +365,14 @@ export const useTable = (props, ...plugins) => {
})
}
- api.getTableProps = userProps =>
- mergeProps(applyPropHooks(api.hooks.getTableProps, api), userProps)
+ instanceRef.current.getTableProps = userProps =>
+ mergeProps(
+ applyPropHooks(
+ instanceRef.current.hooks.getTableProps,
+ instanceRef.current
+ ),
+ userProps
+ )
- return api
-}
-
-function flattenBy(columns, childKey) {
- const flatColumns = []
-
- const recurse = columns => {
- columns.forEach(d => {
- if (!d[childKey]) {
- flatColumns.push(d)
- } else {
- recurse(d[childKey])
- }
- })
- }
-
- recurse(columns)
-
- return flatColumns
+ return instanceRef.current
}
diff --git a/src/plugin-hooks/useExpanded.js b/src/plugin-hooks/useExpanded.js
index 8fa8e0e..436b578 100755
--- a/src/plugin-hooks/useExpanded.js
+++ b/src/plugin-hooks/useExpanded.js
@@ -38,45 +38,48 @@ export const useExpanded = props => {
}, actions.toggleExpanded)
}
- hooks.row.push(row => {
+ hooks.prepareRow.push(row => {
const { path } = row
row.toggleExpanded = set => toggleExpandedByPath(path, set)
return row
})
- const expandedRows = useMemo(() => {
- if (debug) console.info('getExpandedRows')
+ const expandedRows = useMemo(
+ () => {
+ if (debug) console.info('getExpandedRows')
- const expandedRows = []
+ const expandedRows = []
- // Here we do some mutation, but it's the last stage in the
- // immutable process so this is safe
- const handleRow = (row, depth = 0, parentPath = []) => {
- // Compute some final state for the row
- const path = [...parentPath, row.index]
+ // Here we do some mutation, but it's the last stage in the
+ // immutable process so this is safe
+ const handleRow = (row, depth = 0, parentPath = []) => {
+ // Compute some final state for the row
+ const path = [...parentPath, row.index]
- row.path = path
- row.depth = depth
+ row.path = path
+ row.depth = depth
- row.isExpanded =
- (row.original && row.original[manualExpandedKey]) ||
- getBy(expanded, path)
+ row.isExpanded =
+ (row.original && row.original[manualExpandedKey]) ||
+ getBy(expanded, path)
- if (paginateSubRows || (!paginateSubRows && row.depth === 0)) {
- expandedRows.push(row)
+ if (paginateSubRows || (!paginateSubRows && row.depth === 0)) {
+ expandedRows.push(row)
+ }
+
+ if (row.isExpanded && row.subRows && row.subRows.length) {
+ row.subRows.forEach((row, i) => handleRow(row, depth + 1, path))
+ }
+
+ return row
}
- if (row.isExpanded && row.subRows && row.subRows.length) {
- row.subRows.forEach((row, i) => handleRow(row, depth + 1, path))
- }
+ rows.forEach(row => handleRow(row))
- return row
- }
-
- rows.forEach(row => handleRow(row))
-
- return expandedRows
- }, [debug, rows, manualExpandedKey, expanded, paginateSubRows])
+ return expandedRows
+ },
+ [debug, rows, manualExpandedKey, expanded, paginateSubRows]
+ )
const expandedDepth = findExpandedDepth(expanded)
diff --git a/src/plugin-hooks/useFilters.js b/src/plugin-hooks/useFilters.js
index 4f3d8e5..d4b1197 100755
--- a/src/plugin-hooks/useFilters.js
+++ b/src/plugin-hooks/useFilters.js
@@ -22,8 +22,12 @@ const propTypes = {
manualFilters: PropTypes.bool,
}
-export const useFilters = props => {
- PropTypes.checkPropTypes(propTypes, props, 'property', 'useFilters')
+export const useFilters = hooks => {
+ hooks.useMain.push(useMain)
+}
+
+function useMain(instance) {
+ PropTypes.checkPropTypes(propTypes, instance, 'property', 'useFilters')
const {
debug,
@@ -32,9 +36,8 @@ export const useFilters = props => {
filterTypes: userFilterTypes,
manualFilters,
disableFilters,
- hooks,
state: [{ filters }, setState],
- } = props
+ } = instance
const setFilter = (id, updater) => {
const column = columns.find(d => d.id === id)
@@ -94,28 +97,24 @@ export const useFilters = props => {
}, actions.setAllFilters)
}
- hooks.columns.push(columns => {
- columns.forEach(column => {
- const { id, accessor, disableFilters: columnDisableFilters } = column
+ columns.forEach(column => {
+ const { id, accessor, disableFilters: columnDisableFilters } = column
- // Determine if a column is filterable
- column.canFilter = accessor
- ? getFirstDefined(
- columnDisableFilters,
- disableFilters === true ? false : undefined,
- true
- )
- : false
+ // Determine if a column is filterable
+ column.canFilter = accessor
+ ? getFirstDefined(
+ columnDisableFilters,
+ disableFilters === true ? false : undefined,
+ true
+ )
+ : false
- // Provide the column a way of updating the filter value
- column.setFilter = val => setFilter(column.id, val)
+ // Provide the column a way of updating the filter value
+ column.setFilter = val => setFilter(column.id, val)
- // Provide the current filter value to the column for
- // convenience
- column.filterValue = filters[id]
- })
-
- return columns
+ // Provide the current filter value to the column for
+ // convenience
+ column.filterValue = filters[id]
})
// TODO: Create a filter cache for incremental high speed multi-filtering
@@ -199,7 +198,7 @@ export const useFilters = props => {
)
return {
- ...props,
+ ...instance,
setFilter,
setAllFilters,
preFilteredRows: rows,
diff --git a/src/plugin-hooks/useGroupBy.js b/src/plugin-hooks/useGroupBy.js
index 6360a15..aefe02f 100755
--- a/src/plugin-hooks/useGroupBy.js
+++ b/src/plugin-hooks/useGroupBy.js
@@ -48,7 +48,7 @@ export const useGroupBy = props => {
// Sort grouped columns to the start of the column list
// before the headers are built
- hooks.columnsBeforeHeaderGroups.push(columns => {
+ hooks.useColumnsBeforeHeaderGroups.push(columns => {
// eslint-disable-next-line react-hooks/rules-of-hooks
return React.useMemo(
() => [
@@ -92,7 +92,7 @@ export const useGroupBy = props => {
}, actions.toggleGroupBy)
}
- hooks.columns.push(columns => {
+ hooks.useColumns.push(columns => {
columns.forEach(column => {
if (column.canGroupBy) {
column.toggleGroupBy = () => toggleGroupBy(column.id)
@@ -128,8 +128,8 @@ export const useGroupBy = props => {
return columns
}
- hooks.columns.push(addGroupByToggleProps)
- hooks.headers.push(addGroupByToggleProps)
+ hooks.useColumns.push(addGroupByToggleProps)
+ hooks.useHeaders.push(addGroupByToggleProps)
const groupedRows = useMemo(
() => {
diff --git a/src/plugin-hooks/useSortBy.js b/src/plugin-hooks/useSortBy.js
index 74192e0..cf68863 100755
--- a/src/plugin-hooks/useSortBy.js
+++ b/src/plugin-hooks/useSortBy.js
@@ -1,4 +1,4 @@
-import { useMemo } from 'react'
+import React from 'react'
import PropTypes from 'prop-types'
import { addActions, actions } from '../actions'
@@ -34,8 +34,12 @@ const propTypes = {
disableMultiRemove: PropTypes.bool,
}
-export const useSortBy = props => {
- PropTypes.checkPropTypes(propTypes, props, 'property', 'useSortBy')
+export const useSortBy = hooks => {
+ hooks.useMain.push(useMain)
+}
+
+function useMain(instance) {
+ PropTypes.checkPropTypes(propTypes, instance, 'property', 'useSortBy')
const {
debug,
@@ -51,7 +55,7 @@ export const useSortBy = props => {
hooks,
state: [{ sortBy }, setState],
plugins,
- } = props
+ } = instance
// If useSortBy should probably come after useFilters for
// the best performance, so let's hint to the user about that...
@@ -67,16 +71,8 @@ export const useSortBy = props => {
)
}
- columns.forEach(column => {
- const { accessor, disableSorting: columnDisableSorting } = column
- column.canSortBy = accessor
- ? getFirstDefined(
- columnDisableSorting,
- disableSorting === true ? false : undefined,
- true
- )
- : false
- })
+ // Add custom hooks
+ hooks.getSortByToggleProps = []
// Updates sorting based on a columnID, desc flag and multi flag
const toggleSortBy = (columnID, desc, multi) => {
@@ -164,63 +160,58 @@ export const useSortBy = props => {
}, actions.sortByChange)
}
- hooks.columns.push(columns => {
- columns.forEach(column => {
- if (column.canSortBy) {
- column.toggleSortBy = (desc, multi) =>
- toggleSortBy(column.id, desc, multi)
- }
- })
- return columns
- })
+ // Add the getSortByToggleProps method to columns and headers
+ ;[...instance.columns, ...instance.headers].forEach(column => {
+ const { accessor, disableSorting: columnDisableSorting, id } = column
- hooks.getSortByToggleProps = []
-
- const addSortByToggleProps = (columns, api) => {
- columns.forEach(column => {
- const { canSortBy } = column
- column.getSortByToggleProps = props => {
- return mergeProps(
- {
- onClick: canSortBy
- ? e => {
- e.persist()
- column.toggleSortBy(
- undefined,
- !api.disableMultiSort && e.shiftKey
- )
- }
- : undefined,
- style: {
- cursor: canSortBy ? 'pointer' : undefined,
- },
- title: 'Toggle SortBy',
- },
- applyPropHooks(api.hooks.getSortByToggleProps, column, api),
- props
+ const canSort = accessor
+ ? getFirstDefined(
+ columnDisableSorting,
+ disableSorting === true ? false : undefined,
+ true
)
- }
- })
- return columns
- }
+ : false
- hooks.columns.push(addSortByToggleProps)
- hooks.headers.push(addSortByToggleProps)
+ column.canSort = canSort
+
+ if (column.canSort) {
+ column.toggleSortBy = (desc, multi) =>
+ toggleSortBy(column.id, desc, multi)
+ }
+
+ column.getSortByToggleProps = props => {
+ return mergeProps(
+ {
+ onClick: canSort
+ ? e => {
+ e.persist()
+ column.toggleSortBy(
+ undefined,
+ !instance.disableMultiSort && e.shiftKey
+ )
+ }
+ : undefined,
+ style: {
+ cursor: canSort ? 'pointer' : undefined,
+ },
+ title: 'Toggle SortBy',
+ },
+ applyPropHooks(instance.hooks.getSortByToggleProps, column, instance),
+ props
+ )
+ }
- // Mutate columns to reflect sorting state
- columns.forEach(column => {
- const { id } = column
column.sorted = sortBy.find(d => d.id === id)
column.sortedIndex = sortBy.findIndex(d => d.id === id)
column.sortedDesc = column.sorted ? column.sorted.desc : undefined
})
- const sortedRows = useMemo(
+ const sortedRows = React.useMemo(
() => {
if (manualSorting || !sortBy.length) {
return rows
}
- if (debug) console.info('getSortedRows')
+ if (debug) console.time('getSortedRows')
const sortData = rows => {
// Use the orderByFn to compose multiple sortBy's together.
@@ -270,6 +261,8 @@ export const useSortBy = props => {
row.subRows = sortData(row.subRows)
})
+ if (debug) console.timeEnd('getSortedRows')
+
return sortedData
}
@@ -279,7 +272,7 @@ export const useSortBy = props => {
)
return {
- ...props,
+ ...instance,
toggleSortBy,
rows: sortedRows,
preSortedRows: rows,
diff --git a/src/utils.js b/src/utils.js
index 3534a6b..f1e0658 100755
--- a/src/utils.js
+++ b/src/utils.js
@@ -260,7 +260,15 @@ export const mergeProps = (...groups) => {
}
export const applyHooks = (hooks, initial, ...args) =>
- hooks.reduce((prev, next) => next(prev, ...args), initial)
+ hooks.reduce((prev, next) => {
+ const nextValue = next(prev, ...args)
+ if (typeof nextValue === 'undefined') {
+ throw new Error(
+ 'React Table: A hook just returned undefined! This is not allowed.'
+ )
+ }
+ return nextValue
+ }, initial)
export const applyPropHooks = (hooks, ...args) =>
hooks.reduce((prev, next) => mergeProps(prev, next(...args)), {})
@@ -285,6 +293,24 @@ export function isFunction(a) {
}
}
+export function flattenBy(columns, childKey) {
+ const flatColumns = []
+
+ const recurse = columns => {
+ columns.forEach(d => {
+ if (!d[childKey]) {
+ flatColumns.push(d)
+ } else {
+ recurse(d[childKey])
+ }
+ })
+ }
+
+ recurse(columns)
+
+ return flatColumns
+}
+
//
function makePathArray(obj) {