diff --git a/.size-snapshot.json b/.size-snapshot.json index 5a21dc4..88c11b8 100644 --- a/.size-snapshot.json +++ b/.size-snapshot.json @@ -1,20 +1,20 @@ { "dist/index.js": { - "bundled": 108756, - "minified": 50398, - "gzipped": 13421 + "bundled": 112754, + "minified": 52324, + "gzipped": 13765 }, "dist/index.es.js": { - "bundled": 107845, - "minified": 49586, - "gzipped": 13253, + "bundled": 111817, + "minified": 51488, + "gzipped": 13599, "treeshaked": { "rollup": { "code": 80, "import_statements": 21 }, "webpack": { - "code": 8168 + "code": 8484 } } }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 224091b..649273e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 7.0.0-rc.15 + +- Added `useGlobalFilter` hook for performing table-wide filtering +- Filter function signature has changed to supply an array of column IDs (to support both the tranditional single column style and the new multi-column search style introduced with `useGlobalFilter`). +- Removed the `column` parameter from the filter function signature as it was unused and no longer made sense with the array of IDs change above. +- Updated the `filtering` example to use a global filter in addition to the column filters + ## 7.0.0-rc.14 - Changed the function signature for all propGetter hooks to accept a single object of named meta properties instead of a variable length of meta arguments. The user props object has also been added as a property to all prop getters. For example, `hooks.getRowProps.push((props, instance, row) => [...])` is now written `hooks.getRowProps.push((props, { instance, row, userProps }) => [...])` diff --git a/docs/api/useFilters.md b/docs/api/useFilters.md index b0aa22e..8c40a12 100644 --- a/docs/api/useFilters.md +++ b/docs/api/useFilters.md @@ -3,7 +3,7 @@ - Plugin Hook - Optional -`useFilters` is the hook that implements **row filtering**. +`useFilters` is the hook that implements **row filtering** and can even be used in conjunction with `useGlobalFilter`. It's also important to note that this hook can be used either **before or after** `useGlobalFilter`, depending on the performance characteristics you want to code for. ### Table Options diff --git a/docs/api/useGlobalFilter.md b/docs/api/useGlobalFilter.md new file mode 100644 index 0000000..059a82c --- /dev/null +++ b/docs/api/useGlobalFilter.md @@ -0,0 +1,52 @@ +# `useGlobalFilter` + +- Plugin Hook +- Optional + +`useGlobalFilter` is the hook that implements **global row filtering** and can even be used in conjunction with `useFilters`. It's also important to note that this hook can be used either **before or after** `useFilters`, depending on the performance characteristics you want to code for. + +### Table Options + +The following options are supported via the main options object passed to `useTable(options)` + +- `initialState.globalFilter: any` + - Must be **memoized** + - An array of objects containing columnId's and their corresponding filter values. This information is stored in state since the table is allowed to manipulate the filter through user interaction. +- `globalFilter: String | Function` + - Optional + - Defaults to `text` + - The resolved function from the this string/function will be used to filter the table's data. + - If a `string` is passed, the function with that name located on either the custom `filterTypes` option or the built-in filtering types object will be used. If + - If a `function` is passed, it will be used directly. + - For more information on filter types, see Filtering + - If a **function** is passed, it must be **memoized** +- `manualGlobalFilter: Bool` + - Enables filter detection functionality, but does not automatically perform row filtering. + - Turn this on if you wish to implement your own row filter outside of the table (eg. server-side or manual row grouping/nesting) +- `filterTypes: Object` + - Must be **memoized** + - Allows overriding or adding additional filter types for the table to use. If the globalFilter type isn't found on this object, it will default to using the built-in filter types. + - For more information on filter types, see Filtering +- `autoResetGlobalFilter: Boolean` + - Defaults to `true` + - When `true`, the `globalFilter` state will automatically reset if any of the following conditions are met: + - `data` is changed + - To disable, set to `false` + - For more information see the FAQ ["How do I stop my table state from automatically resetting when my data changes?"](./faq#how-do-i-stop-my-table-state-from-automatically-resetting-when-my-data-changes) + +### Instance Properties + +The following values are provided to the table `instance`: + +- `rows: Array` + - An array of **filtered** rows. +- `preGlobalFilteredRows: Array` + - The array of rows **used right before filtering**. + - Among many other use-cases, these rows are directly useful for building option lists in filters, since the resulting filtered `rows` do not contain every possible option. +- `setGlobalFilter: Function(columnId, filterValue) => void` + - An instance-level function used to update the filter value for a specific column. + +### Example + +- [Source](https://github.com/tannerlinsley/react-table/tree/master/examples/filtering) +- [Open in CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/filtering) diff --git a/examples/filtering/src/App.js b/examples/filtering/src/App.js index 94baf78..4eb7a23 100644 --- a/examples/filtering/src/App.js +++ b/examples/filtering/src/App.js @@ -1,6 +1,6 @@ import React from 'react' import styled from 'styled-components' -import { useTable, useFilters } from 'react-table' +import { useTable, useFilters, useGlobalFilter } from 'react-table' // A great library for fuzzy filtering/sorting items import matchSorter from 'match-sorter' @@ -35,6 +35,32 @@ const Styles = styled.div` } ` +// Define a default UI for filtering +function GlobalFilter({ + preGlobalFilteredRows, + globalFilter, + setGlobalFilter, +}) { + const count = preGlobalFilteredRows.length + + return ( + + Search:{' '} + { + setGlobalFilter(e.target.value || undefined) // Set undefined to remove the filter entirely + }} + placeholder={`${count} records...`} + style={{ + fontSize: '1.1rem', + border: '0', + }} + /> + + ) +} + // Define a default UI for filtering function DefaultColumnFilter({ column: { filterValue, preFilteredRows, setFilter }, @@ -217,6 +243,9 @@ function Table({ columns, data }) { rows, prepareRow, state, + flatColumns, + preGlobalFilteredRows, + setGlobalFilter, } = useTable( { columns, @@ -224,7 +253,8 @@ function Table({ columns, data }) { defaultColumn, // Be sure to pass the defaultColumn option filterTypes, }, - useFilters // useFilters! + useFilters, // useFilters! + useGlobalFilter // useGlobalFilter! ) // We don't want to render all of the rows for this example, so cap @@ -233,11 +263,6 @@ function Table({ columns, data }) { return ( <> -
-
-          {JSON.stringify(state.filters, null, 2)}
-        
-
{headerGroups.map(headerGroup => ( @@ -251,25 +276,41 @@ function Table({ columns, data }) { ))} ))} + + + - {firstPageRows.map( - (row, i) => { - prepareRow(row); - return ( - - {row.cells.map(cell => { - return ( - - ) - })} - - )} - )} + {firstPageRows.map((row, i) => { + prepareRow(row) + return ( + + {row.cells.map(cell => { + return + })} + + ) + })}
+ +
{cell.render('Cell')}
{cell.render('Cell')}

Showing the first 20 results of {rows.length} rows
+
+
+          {JSON.stringify(state.filters, null, 2)}
+        
+
) } diff --git a/examples/filtering/yarn.lock b/examples/filtering/yarn.lock index 05e78cf..c5fa9f7 100644 --- a/examples/filtering/yarn.lock +++ b/examples/filtering/yarn.lock @@ -8094,10 +8094,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.14" + resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.0.0-rc.14.tgz#bb4171fbdd56d78dd5f6b9e18694a36c00bd6261" + integrity sha512-9NyzAE0kLH8HA+DK86ynxVDxniO5ZdggAqI7nDKauWXQGVYNFId8+JJSescJ2vwP9nR2JKyCEDG9c+CjIaLNkA== react@^16.8.6: version "16.8.6" diff --git a/src/filterTypes.js b/src/filterTypes.js index e409b62..d436e91 100644 --- a/src/filterTypes.js +++ b/src/filterTypes.js @@ -1,79 +1,93 @@ -export const text = (rows, id, filterValue) => { +export const text = (rows, ids, filterValue) => { rows = rows.filter(row => { - const rowValue = row.values[id] - return String(rowValue) - .toLowerCase() - .includes(String(filterValue).toLowerCase()) + return ids.some(id => { + const rowValue = row.values[id] + return String(rowValue) + .toLowerCase() + .includes(String(filterValue).toLowerCase()) + }) }) return rows } text.autoRemove = val => !val -export const exactText = (rows, id, filterValue) => { +export const exactText = (rows, ids, filterValue) => { return rows.filter(row => { - const rowValue = row.values[id] - return rowValue !== undefined - ? String(rowValue).toLowerCase() === String(filterValue).toLowerCase() - : true + return ids.some(id => { + const rowValue = row.values[id] + return rowValue !== undefined + ? String(rowValue).toLowerCase() === String(filterValue).toLowerCase() + : true + }) }) } exactText.autoRemove = val => !val -export const exactTextCase = (rows, id, filterValue) => { +export const exactTextCase = (rows, ids, filterValue) => { return rows.filter(row => { - const rowValue = row.values[id] - return rowValue !== undefined - ? String(rowValue) === String(filterValue) - : true + return ids.some(id => { + const rowValue = row.values[id] + return rowValue !== undefined + ? String(rowValue) === String(filterValue) + : true + }) }) } exactTextCase.autoRemove = val => !val -export const includes = (rows, id, filterValue) => { +export const includes = (rows, ids, filterValue) => { return rows.filter(row => { - const rowValue = row.values[id] - return filterValue.includes(rowValue) + return ids.some(id => { + const rowValue = row.values[id] + return filterValue.includes(rowValue) + }) }) } includes.autoRemove = val => !val || !val.length -export const includesAll = (rows, id, filterValue) => { +export const includesAll = (rows, ids, filterValue) => { return rows.filter(row => { - const rowValue = row.values[id] - return ( - rowValue && - rowValue.length && - filterValue.every(val => rowValue.includes(val)) - ) + return ids.some(id => { + const rowValue = row.values[id] + return ( + rowValue && + rowValue.length && + filterValue.every(val => rowValue.includes(val)) + ) + }) }) } includesAll.autoRemove = val => !val || !val.length -export const exact = (rows, id, filterValue) => { +export const exact = (rows, ids, filterValue) => { return rows.filter(row => { - const rowValue = row.values[id] - return rowValue === filterValue + return ids.some(id => { + const rowValue = row.values[id] + return rowValue === filterValue + }) }) } exact.autoRemove = val => typeof val === 'undefined' -export const equals = (rows, id, filterValue) => { +export const equals = (rows, ids, filterValue) => { return rows.filter(row => { - const rowValue = row.values[id] - // eslint-disable-next-line eqeqeq - return rowValue == filterValue + return ids.some(id => { + const rowValue = row.values[id] + // eslint-disable-next-line eqeqeq + return rowValue == filterValue + }) }) } equals.autoRemove = val => val == null -export const between = (rows, id, filterValue) => { +export const between = (rows, ids, filterValue) => { let [min, max] = filterValue || [] min = typeof min === 'number' ? min : -Infinity @@ -86,8 +100,10 @@ export const between = (rows, id, filterValue) => { } return rows.filter(row => { - const rowValue = row.values[id] - return rowValue >= min && rowValue <= max + return ids.some(id => { + const rowValue = row.values[id] + return rowValue >= min && rowValue <= max + }) }) } diff --git a/src/hooks/useColumnVisibility.js b/src/hooks/useColumnVisibility.js index c48663a..a3c28a1 100644 --- a/src/hooks/useColumnVisibility.js +++ b/src/hooks/useColumnVisibility.js @@ -79,8 +79,6 @@ function reducer(state, action, previousState, instance) { ? action.value : !state.hiddenColumns.includes(action.columnId) - console.log(action, should) - const hiddenColumns = should ? [...state.hiddenColumns, action.columnId] : state.hiddenColumns.filter(d => d !== action.columnId) diff --git a/src/index.js b/src/index.js index 933440b..228a6cb 100755 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ export * from './publicUtils' export { useTable } from './hooks/useTable' export { useExpanded } from './plugin-hooks/useExpanded' export { useFilters } from './plugin-hooks/useFilters' +export { useGlobalFilter } from './plugin-hooks/useGlobalFilter' export { useGroupBy } from './plugin-hooks/useGroupBy' export { useSortBy } from './plugin-hooks/useSortBy' export { usePagination } from './plugin-hooks/usePagination' diff --git a/src/plugin-hooks/tests/__snapshots__/useFilters.test.js.snap b/src/plugin-hooks/tests/__snapshots__/useFilters.test.js.snap index 8ef55c3..76db8ed 100644 --- a/src/plugin-hooks/tests/__snapshots__/useFilters.test.js.snap +++ b/src/plugin-hooks/tests/__snapshots__/useFilters.test.js.snap @@ -1,151 +1,766 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders a filterable table 1`] = ` -Snapshot Diff: -- First value -+ Second value - -@@ -27,11 +27,11 @@ - colspan="1" - > - Last Name + + + + + + + + + + + + + + + + + - - -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Info +
+ First Name + + + Last Name + + + Age + + + Visits + + + Status + + + Profile Progress + +
+ - -@@ -87,50 +87,10 @@ - - status: In Relationship - - progress: 50 --
-- firstName: derek -- -- lastName: perkins -- -- age: 40 -- -- visits: 40 -- -- status: Single -- -- progress: 80 --
-- firstName: joe -- -- lastName: bergevin -- -- age: 45 -- -- visits: 20 -- -- status: Complicated -- -- progress: 10 -
- firstName: jaylen + + +
+ firstName: tanner + + lastName: linsley + + age: 29 + + visits: 100 + + status: In Relationship + + progress: 50 +
+ firstName: derek + + lastName: perkins + + age: 40 + + visits: 40 + + status: Single + + progress: 80 +
+ firstName: joe + + lastName: bergevin + + age: 45 + + visits: 20 + + status: Complicated + + progress: 10 +
+ firstName: jaylen + + lastName: linsley + + age: 26 + + visits: 99 + + status: In Relationship + + progress: 70 +
+
`; exports[`renders a filterable table 2`] = ` -Snapshot Diff: -- First value -+ Second value - -@@ -27,11 +27,11 @@ - colspan="1" - > - Last Name + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - -
+ Name + + Info +
+ First Name + + + Last Name + + + Age + + + Visits + + + Status + + + Profile Progress + +
+ - -@@ -71,46 +71,46 @@ -
-- firstName: tanner -+ firstName: derek - -- lastName: linsley -+ lastName: perkins - -- age: 29 -+ age: 40 - -- visits: 100 -+ visits: 40 - -- status: In Relationship -+ status: Single - -- progress: 50 -+ progress: 80 -
-- firstName: jaylen -+ firstName: joe - -- lastName: linsley -+ lastName: bergevin - -- age: 26 -+ age: 45 - -- visits: 99 -+ visits: 20 - -- status: In Relationship -+ status: Complicated - -- progress: 70 -+ progress: 10 -
-
+ + + + + + + + firstName: tanner + + + lastName: linsley + + + age: 29 + + + visits: 100 + + + status: In Relationship + + + progress: 50 + + + + + firstName: jaylen + + + lastName: linsley + + + age: 26 + + + visits: 99 + + + status: In Relationship + + + progress: 70 + + + + + +`; + +exports[`renders a filterable table 3`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Info +
+ First Name + + + Last Name + + + Age + + + Visits + + + Status + + + Profile Progress + +
+ + + +
+ firstName: derek + + lastName: perkins + + age: 40 + + visits: 40 + + status: Single + + progress: 80 +
+ firstName: joe + + lastName: bergevin + + age: 45 + + visits: 20 + + status: Complicated + + progress: 10 +
+
+`; + +exports[`renders a filterable table 4`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Info +
+ First Name + + + Last Name + + + Age + + + Visits + + + Status + + + Profile Progress + +
+ + + +
+ firstName: tanner + + lastName: linsley + + age: 29 + + visits: 100 + + status: In Relationship + + progress: 50 +
+ firstName: derek + + lastName: perkins + + age: 40 + + visits: 40 + + status: Single + + progress: 80 +
+ firstName: joe + + lastName: bergevin + + age: 45 + + visits: 20 + + status: Complicated + + progress: 10 +
+ firstName: jaylen + + lastName: linsley + + age: 26 + + visits: 99 + + status: In Relationship + + progress: 70 +
+
+`; + +exports[`renders a filterable table 5`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Info +
+ First Name + + + Last Name + + + Age + + + Visits + + + Status + + + Profile Progress + +
+ + + +
+ firstName: tanner + + lastName: linsley + + age: 29 + + visits: 100 + + status: In Relationship + + progress: 50 +
+ firstName: joe + + lastName: bergevin + + age: 45 + + visits: 20 + + status: Complicated + + progress: 10 +
+ firstName: jaylen + + lastName: linsley + + age: 26 + + visits: 99 + + status: In Relationship + + progress: 70 +
+
`; diff --git a/src/plugin-hooks/tests/useFilters.test.js b/src/plugin-hooks/tests/useFilters.test.js index bed29f5..b53b40e 100644 --- a/src/plugin-hooks/tests/useFilters.test.js +++ b/src/plugin-hooks/tests/useFilters.test.js @@ -2,6 +2,7 @@ import React from 'react' import { render, fireEvent } from '@testing-library/react' import { useTable } from '../../hooks/useTable' import { useFilters } from '../useFilters' +import { useGlobalFilter } from '../useGlobalFilter' const data = [ { @@ -58,13 +59,17 @@ function Table({ columns, data }) { headerGroups, rows, prepareRow, + flatColumns, + state, + setGlobalFilter, } = useTable( { columns, data, defaultColumn, }, - useFilters + useFilters, + useGlobalFilter ) return ( @@ -80,6 +85,28 @@ function Table({ columns, data }) { ))} ))} + + + + { + setGlobalFilter(e.target.value || undefined) // Set undefined to remove the filter entirely + }} + placeholder={`Global search...`} + style={{ + fontSize: '1.1rem', + border: '0', + }} + /> + + + {rows.map( @@ -142,8 +169,11 @@ function App() { } test('renders a filterable table', () => { - const { getAllByPlaceholderText, asFragment } = render() + const { getAllByPlaceholderText, getByPlaceholderText, asFragment } = render( + + ) + const globalFilterInput = getByPlaceholderText('Global search...') const filterInputs = getAllByPlaceholderText('Search...') const beforeFilter = asFragment() @@ -156,6 +186,17 @@ test('renders a filterable table', () => { const afterFilter2 = asFragment() - expect(beforeFilter).toMatchDiffSnapshot(afterFilter1) - expect(afterFilter1).toMatchDiffSnapshot(afterFilter2) + fireEvent.change(filterInputs[1], { target: { value: '' } }) + + const afterFilter3 = asFragment() + + fireEvent.change(globalFilterInput, { target: { value: 'li' } }) + + const afterFilter4 = asFragment() + + expect(beforeFilter).toMatchSnapshot() + expect(afterFilter1).toMatchSnapshot() + expect(afterFilter2).toMatchSnapshot() + expect(afterFilter3).toMatchSnapshot() + expect(afterFilter4).toMatchSnapshot() }) diff --git a/src/plugin-hooks/useFilters.js b/src/plugin-hooks/useFilters.js index 18089bf..06871a4 100755 --- a/src/plugin-hooks/useFilters.js +++ b/src/plugin-hooks/useFilters.js @@ -3,10 +3,11 @@ import React from 'react' import { actions, getFirstDefined, - isFunction, + getFilterMethod, useMountedLayoutEffect, functionalUpdate, useGetLatest, + shouldAutoRemoveFilter, } from '../utils' import * as filterTypes from '../filterTypes' @@ -63,7 +64,7 @@ function reducer(state, action, previousState, instance) { ) // - if (shouldAutoRemove(filterMethod.autoRemove, newFilter)) { + if (shouldAutoRemoveFilter(filterMethod.autoRemove, newFilter)) { return { ...state, filters: state.filters.filter(d => d.id !== columnId), @@ -103,7 +104,7 @@ function reducer(state, action, previousState, instance) { filterTypes ) - if (shouldAutoRemove(filterMethod.autoRemove, filter.value)) { + if (shouldAutoRemoveFilter(filterMethod.autoRemove, filter.value)) { return false } return true @@ -164,17 +165,9 @@ function useInstance(instance) { column.filterValue = found && found.value }) - // TODO: Create a filter cache for incremental high speed multi-filtering - // This gets pretty complicated pretty fast, since you have to maintain a - // 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 { filteredRows, filteredFlatRows } = React.useMemo(() => { + const [filteredRows, filteredFlatRows] = React.useMemo(() => { if (manualFilters || !filters.length) { - return { - filteredRows: rows, - filteredFlatRows: flatRows, - } + return [rows, flatRows] } const filteredFlatRows = [] @@ -213,9 +206,8 @@ function useInstance(instance) { // to get the filtered rows back column.filteredRows = filterMethod( filteredSoFar, - columnId, - filterValue, - column + [columnId], + filterValue ) return column.filteredRows @@ -244,10 +236,7 @@ function useInstance(instance) { return filteredRows } - return { - filteredRows: filterRows(rows), - filteredFlatRows, - } + return [filterRows(rows), filteredFlatRows] }, [manualFilters, filters, rows, flatRows, flatColumns, userFilterTypes]) React.useMemo(() => { @@ -284,16 +273,3 @@ function useInstance(instance) { setAllFilters, }) } - -function shouldAutoRemove(autoRemove, value) { - return autoRemove ? autoRemove(value) : typeof value === 'undefined' -} - -function getFilterMethod(filter, userFilterTypes, filterTypes) { - return ( - isFunction(filter) || - userFilterTypes[filter] || - filterTypes[filter] || - filterTypes.text - ) -} diff --git a/src/plugin-hooks/useGlobalFilter.js b/src/plugin-hooks/useGlobalFilter.js new file mode 100644 index 0000000..3e3d33b --- /dev/null +++ b/src/plugin-hooks/useGlobalFilter.js @@ -0,0 +1,152 @@ +import React from 'react' + +import { + actions, + getFilterMethod, + useMountedLayoutEffect, + functionalUpdate, + useGetLatest, + shouldAutoRemoveFilter, + ensurePluginOrder, +} from '../utils' +import * as filterTypes from '../filterTypes' + +// Actions +actions.resetGlobalFilter = 'resetGlobalFilter' +actions.setGlobalFilter = 'setGlobalFilter' + +export const useGlobalFilter = hooks => { + hooks.stateReducers.push(reducer) + hooks.useInstance.push(useInstance) +} + +useGlobalFilter.pluginName = 'useGlobalFilter' + +function reducer(state, action, previousState, instance) { + if (action.type === actions.resetGlobalFilter) { + return { + ...state, + globalFilter: instance.initialState.globalFilter || undefined, + } + } + + if (action.type === actions.setGlobalFilter) { + const { filterValue } = action + const { userFilterTypes } = instance + + const filterMethod = getFilterMethod( + instance.globalFilter, + userFilterTypes || {}, + filterTypes + ) + + const newFilter = functionalUpdate(filterValue, state.globalFilter) + + // + if (shouldAutoRemoveFilter(filterMethod.autoRemove, newFilter)) { + const { globalFilter, ...stateWithoutGlobalFilter } = state + return stateWithoutGlobalFilter + } + + return { + ...state, + globalFilter: newFilter, + } + } +} + +function useInstance(instance) { + const { + data, + rows, + flatRows, + flatColumns, + filterTypes: userFilterTypes, + globalFilter, + manualGlobalFilter, + state: { globalFilter: globalFilterValue }, + dispatch, + autoResetGlobalFilters = true, + plugins, + } = instance + + ensurePluginOrder(plugins, [], 'useGlobalFilter', [ + 'useSortBy', + 'useExpanded', + ]) + + const setGlobalFilter = filterValue => { + dispatch({ type: actions.setGlobalFilter, filterValue }) + } + + // TODO: Create a filter cache for incremental high speed multi-filtering + // This gets pretty complicated pretty fast, since you have to maintain a + // 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(() => { + if (manualGlobalFilter || typeof globalFilterValue === 'undefined') { + return [rows, flatRows] + } + + const filteredFlatRows = [] + + const filterMethod = getFilterMethod( + globalFilter, + userFilterTypes || {}, + filterTypes + ) + + if (!filterMethod) { + console.warn(`Could not find a valid 'globalFilter' option.`) + return rows + } + + // Filters top level and nested rows + const filterRows = filteredRows => { + return filterMethod( + filteredRows, + flatColumns.map(d => d.id), + globalFilterValue + ).map(row => { + filteredFlatRows.push(row) + + return { + ...row, + subRows: + row.subRows && row.subRows.length + ? filterRows(row.subRows) + : row.subRows, + } + }) + } + + return [filterRows(rows), filteredFlatRows] + }, [ + manualGlobalFilter, + globalFilter, + userFilterTypes, + rows, + flatRows, + flatColumns, + globalFilterValue, + ]) + + const getAutoResetGlobalFilters = useGetLatest(autoResetGlobalFilters) + + useMountedLayoutEffect(() => { + if (getAutoResetGlobalFilters()) { + dispatch({ type: actions.resetGlobalFilter }) + } + }, [dispatch, manualGlobalFilter ? null : data]) + + Object.assign(instance, { + preGlobalFilteredRows: rows, + preGlobalFilteredFlatRows: flatRows, + globalFilteredRows, + globalFilteredFlatRows, + rows: globalFilteredRows, + flatRows: globalFilteredFlatRows, + setGlobalFilter, + }) +} diff --git a/src/utils.js b/src/utils.js index 3e76a72..14ed220 100755 --- a/src/utils.js +++ b/src/utils.js @@ -267,6 +267,19 @@ export function expandRows( return expandedRows } +export function getFilterMethod(filter, userFilterTypes, filterTypes) { + return ( + isFunction(filter) || + userFilterTypes[filter] || + filterTypes[filter] || + filterTypes.text + ) +} + +export function shouldAutoRemoveFilter(autoRemove, value) { + return autoRemove ? autoRemove(value) : typeof value === 'undefined' +} + // const reOpenBracket = /\[/g