diff --git a/docs/api/useGlobalFilter.md b/docs/api/useGlobalFilter.md index 6aabc68..9e09bd4 100644 --- a/docs/api/useGlobalFilter.md +++ b/docs/api/useGlobalFilter.md @@ -23,6 +23,8 @@ The following options are supported via the main options object passed to `useTa - `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) +- `disableGlobalFilter: Bool` + - Disables global filtering for every column in the entire table. - `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. @@ -34,6 +36,14 @@ The following options are supported via the main options object passed to `useTa - 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) +### Column Options + +The following options are supported on any `Column` object passed to the `columns` option in `useTable()` + +- `disableGlobalFilter: Bool` + - Optional + - If set to `true`, will disable global filtering for this column + ### Instance Properties The following values are provided to the table `instance`: diff --git a/src/plugin-hooks/tests/useFilters.test.js b/src/plugin-hooks/tests/useFilters.test.js index 425af2b..efc9c98 100644 --- a/src/plugin-hooks/tests/useFilters.test.js +++ b/src/plugin-hooks/tests/useFilters.test.js @@ -52,10 +52,14 @@ const defaultColumn = { ), } -function App() { +function App(props) { const [data, setData] = React.useState(makeData) - const columns = React.useMemo( - () => [ + + const columns = React.useMemo(() => { + if (props.columns) { + return props.columns + } + return [ { Header: 'Name', columns: [ @@ -90,9 +94,8 @@ function App() { }, ], }, - ], - [] - ) + ] + }, [props.columns]) const { getTableProps, @@ -177,6 +180,8 @@ test('renders a filterable table', async () => { const globalFilterInput = rendered.getByPlaceholderText('Global search...') const filterInputs = rendered.getAllByPlaceholderText('Search...') + expect(filterInputs).toHaveLength(6) + fireEvent.change(filterInputs[1], { target: { value: 'l' } }) expect( rendered @@ -243,3 +248,146 @@ test('renders a filterable table', async () => { 'firstName: jaylen', ]) }) + +test('does not filter columns marked as disableFilters', () => { + const columns = [ + { + Header: 'Name', + columns: [ + { + Header: 'First Name', + accessor: 'firstName', + disableFilters: true, + }, + { + Header: 'Last Name', + accessor: 'lastName', + disableFilters: true, + }, + ], + }, + { + Header: 'Info', + columns: [ + { + Header: 'Age', + accessor: 'age', + }, + { + Header: 'Visits', + accessor: 'visits', + }, + { + Header: 'Status', + accessor: 'status', + }, + { + Header: 'Profile Progress', + accessor: 'progress', + }, + ], + }, + ] + const rendered = render() + + const filterInputs = rendered.getAllByPlaceholderText('Search...') + + expect(filterInputs).toHaveLength(4) + + // should be Age column + fireEvent.change(filterInputs[0], { target: { value: '45' } }) + expect( + rendered + .queryAllByRole('row') + .slice(3) + .map(row => Array.from(row.children)[0].textContent) + ).toEqual(['firstName: joe']) + + fireEvent.change(filterInputs[0], { target: { value: '' } }) + expect( + rendered + .queryAllByRole('row') + .slice(3) + .map(row => Array.from(row.children)[0].textContent) + ).toEqual([ + 'firstName: tanner', + 'firstName: derek', + 'firstName: joe', + 'firstName: jaylen', + ]) +}) + +test('does not filter columns with GlobalFilter if marked disableGlobalFilter', () => { + const columns = [ + { + Header: 'Name', + columns: [ + { + Header: 'First Name', + accessor: 'firstName', + disableGlobalFilter: true, + }, + { + Header: 'Last Name', + accessor: 'lastName', + disableGlobalFilter: true, + }, + ], + }, + { + Header: 'Info', + columns: [ + { + Header: 'Age', + accessor: 'age', + }, + { + Header: 'Visits', + accessor: 'visits', + }, + { + Header: 'Status', + accessor: 'status', + }, + { + Header: 'Profile Progress', + accessor: 'progress', + }, + ], + }, + ] + const rendered = render() + + const globalFilterInput = rendered.getByPlaceholderText('Global search...') + + fireEvent.change(globalFilterInput, { target: { value: '' } }) + expect( + rendered + .queryAllByRole('row') + .slice(3) + .map(row => Array.from(row.children)[0].textContent) + ).toEqual([ + 'firstName: tanner', + 'firstName: derek', + 'firstName: joe', + 'firstName: jaylen', + ]) + + // global filter shouldn't apply to firstName or lastName + fireEvent.change(globalFilterInput, { target: { value: 'li' } }) + expect( + rendered + .queryAllByRole('row') + .slice(3) + .map(row => Array.from(row.children)[0].textContent) + ).toEqual(['firstName: joe']) + + // double check global filter ignore (should ignore joe bergevin) + fireEvent.change(globalFilterInput, { target: { value: 'in' } }) + expect( + rendered + .queryAllByRole('row') + .slice(3) + .map(row => Array.from(row.children)[0].textContent) + ).toEqual(['firstName: tanner', 'firstName: derek', 'firstName: jaylen']) +}) diff --git a/src/plugin-hooks/useGlobalFilter.js b/src/plugin-hooks/useGlobalFilter.js index 52ba560..14ac2ae 100644 --- a/src/plugin-hooks/useGlobalFilter.js +++ b/src/plugin-hooks/useGlobalFilter.js @@ -1,6 +1,10 @@ import React from 'react' -import { getFilterMethod, shouldAutoRemoveFilter } from '../utils' +import { + getFilterMethod, + shouldAutoRemoveFilter, + getFirstDefined, +} from '../utils' import { actions, @@ -68,6 +72,7 @@ function useInstance(instance) { state: { globalFilter: globalFilterValue }, dispatch, autoResetGlobalFilter = true, + disableGlobalFilter, } = instance const setGlobalFilter = React.useCallback( @@ -105,11 +110,23 @@ function useInstance(instance) { return rows } + allColumns.forEach(column => { + const { disableGlobalFilter: columnDisableGlobalFilter } = column + + column.canFilter = getFirstDefined( + columnDisableGlobalFilter === true ? false : undefined, + disableGlobalFilter === true ? false : undefined, + true + ) + }) + + const filterableColumns = allColumns.filter(c => c.canFilter === true) + // Filters top level and nested rows const filterRows = filteredRows => { filteredRows = filterMethod( filteredRows, - allColumns.map(d => d.id), + filterableColumns.map(d => d.id), globalFilterValue ) @@ -132,10 +149,11 @@ function useInstance(instance) { globalFilterValue, globalFilter, userFilterTypes, + allColumns, rows, flatRows, rowsById, - allColumns, + disableGlobalFilter, ]) const getAutoResetGlobalFilter = useGetLatest(autoResetGlobalFilter) @@ -157,5 +175,6 @@ function useInstance(instance) { flatRows: globalFilteredFlatRows, rowsById: globalFilteredRowsById, setGlobalFilter, + disableGlobalFilter, }) }