mirror of
https://github.com/gosticks/react-table.git
synced 2025-10-16 11:55:36 +00:00
Upgrade filter types to support configurable autoRemoval
This commit is contained in:
parent
fd8a686b30
commit
90922f8637
17
README.md
17
README.md
@ -333,7 +333,7 @@ The following options are supported on any column object you can pass to `column
|
|||||||
- 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.
|
||||||
|
|
||||||
### Table Output
|
### `Instance` Properties
|
||||||
|
|
||||||
The following properties are available on the table instance returned from `useTable`
|
The following properties are available on the table instance returned from `useTable`
|
||||||
|
|
||||||
@ -531,7 +531,7 @@ The following options are supported via the main options object passed to `useTa
|
|||||||
- Must be **memoized**
|
- Must be **memoized**
|
||||||
- Allows overriding or adding additional aggregation functions for use when grouping/aggregating row values. If an aggregation key isn't found on this object, it will default to using the [built-in aggregation functions](TODO)
|
- Allows overriding or adding additional aggregation functions for use when grouping/aggregating row values. If an aggregation key isn't found on this object, it will default to using the [built-in aggregation functions](TODO)
|
||||||
|
|
||||||
### Instance Variables
|
### `Instance` Properties
|
||||||
|
|
||||||
The following values are provided to the table `instance`:
|
The following values are provided to the table `instance`:
|
||||||
|
|
||||||
@ -589,12 +589,15 @@ The following options are supported via the main options object passed to `useTa
|
|||||||
- Allows overriding or adding additional filter types for columns to use. If a column's filter type isn't found on this object, it will default to using the [built-in filter types](TODO).
|
- Allows overriding or adding additional filter types for columns to use. If a column's filter type isn't found on this object, it will default to using the [built-in filter types](TODO).
|
||||||
- For mor information on filter types, see [Filtering](TODO)
|
- For mor information on filter types, see [Filtering](TODO)
|
||||||
|
|
||||||
### Instance Variables
|
### `Instance` Properties
|
||||||
|
|
||||||
The following values are provided to the table `instance`:
|
The following values are provided to the table `instance`:
|
||||||
|
|
||||||
- `rows: Array<Row>`
|
- `rows: Array<Row>`
|
||||||
- An array of **filtered** rows.
|
- An array of **filtered** rows.
|
||||||
|
- `preFilteredRows: Array<Row>`
|
||||||
|
- 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.
|
||||||
- `setFilter: Function(columnID, filterValue) => void`
|
- `setFilter: Function(columnID, filterValue) => void`
|
||||||
- An instance-level function used to update the filter value for a specific column.
|
- An instance-level function used to update the filter value for a specific column.
|
||||||
- `setAllFilters: Function(filtersObject) => void`
|
- `setAllFilters: Function(filtersObject) => void`
|
||||||
@ -627,16 +630,20 @@ const filterTypes = React.useMemo(() => ({
|
|||||||
}
|
}
|
||||||
}), [matchSorter])
|
}), [matchSorter])
|
||||||
|
|
||||||
|
// Override the default column filter to be our new `fuzzyText` filter type
|
||||||
|
const defaultColumn = React.useMemo(() => ({
|
||||||
|
filter: 'fuzzyText'
|
||||||
|
}))
|
||||||
|
|
||||||
const { rows } = useTable(
|
const { rows } = useTable(
|
||||||
{
|
{
|
||||||
// state[0].groupBy === ['firstName']
|
// state[0].groupBy === ['firstName']
|
||||||
state,
|
state,
|
||||||
// Override the default filter to be our new `fuzzyText` filter type
|
|
||||||
defaultFilter: 'fuzzyText',
|
|
||||||
manualFilters: false,
|
manualFilters: false,
|
||||||
disableFilters: false,
|
disableFilters: false,
|
||||||
// Pass our custom filter types
|
// Pass our custom filter types
|
||||||
filterTypes,
|
filterTypes,
|
||||||
|
defaultColumn
|
||||||
},
|
},
|
||||||
useFilters
|
useFilters
|
||||||
)
|
)
|
||||||
|
|||||||
@ -45,7 +45,7 @@ function Table({ columns, data }) {
|
|||||||
<table {...getTableProps()}>
|
<table {...getTableProps()}>
|
||||||
<thead>
|
<thead>
|
||||||
{headerGroups.map(headerGroup => (
|
{headerGroups.map(headerGroup => (
|
||||||
<tr {...headerGroup.getRowProps()}>
|
<tr {...headerGroup.getHeaderGroupProps()}>
|
||||||
{headerGroup.headers.map(column => (
|
{headerGroup.headers.map(column => (
|
||||||
<th {...column.getHeaderProps()}>{column.render('Header')}</th>
|
<th {...column.getHeaderProps()}>{column.render('Header')}</th>
|
||||||
))}
|
))}
|
||||||
|
|||||||
4
examples/filtering-with-useFilters/.babelrc
Normal file
4
examples/filtering-with-useFilters/.babelrc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"presets": ["react-app"],
|
||||||
|
"plugins": ["styled-components"]
|
||||||
|
}
|
||||||
1
examples/filtering-with-useFilters/.env
Normal file
1
examples/filtering-with-useFilters/.env
Normal file
@ -0,0 +1 @@
|
|||||||
|
SKIP_PREFLIGHT_CHECK=true
|
||||||
7
examples/filtering-with-useFilters/.eslintrc
Normal file
7
examples/filtering-with-useFilters/.eslintrc
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": ["react-app", "prettier"],
|
||||||
|
"rules": {
|
||||||
|
// "eqeqeq": 0,
|
||||||
|
// "jsx-a11y/anchor-is-valid": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
23
examples/filtering-with-useFilters/.gitignore
vendored
Normal file
23
examples/filtering-with-useFilters/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
29
examples/filtering-with-useFilters/.rescriptsrc.js
Normal file
29
examples/filtering-with-useFilters/.rescriptsrc.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const resolveFrom = require('resolve-from')
|
||||||
|
|
||||||
|
const fixLinkedDependencies = config => {
|
||||||
|
config.resolve = {
|
||||||
|
...config.resolve,
|
||||||
|
alias: {
|
||||||
|
...config.resolve.alias,
|
||||||
|
react$: resolveFrom(path.resolve('node_modules'), 'react'),
|
||||||
|
'react-dom$': resolveFrom(path.resolve('node_modules'), 'react-dom'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
const includeSrcDirectory = config => {
|
||||||
|
config.resolve = {
|
||||||
|
...config.resolve,
|
||||||
|
modules: [path.resolve('src'), ...config.resolve.modules],
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
['use-babel-config', '.babelrc'],
|
||||||
|
['use-eslint-config', '.eslintrc'],
|
||||||
|
fixLinkedDependencies,
|
||||||
|
// includeSrcDirectory,
|
||||||
|
]
|
||||||
6
examples/filtering-with-useFilters/README.md
Normal file
6
examples/filtering-with-useFilters/README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app) and Rescripts.
|
||||||
|
|
||||||
|
You can:
|
||||||
|
|
||||||
|
- [Open this example in a new CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/basic)
|
||||||
|
- `yarn` and `yarn start` to run and edit the example
|
||||||
36
examples/filtering-with-useFilters/package.json
Normal file
36
examples/filtering-with-useFilters/package.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"start": "rescripts start",
|
||||||
|
"build": "rescripts build",
|
||||||
|
"test": "rescripts test",
|
||||||
|
"eject": "rescripts eject"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"match-sorter": "^4.0.0",
|
||||||
|
"namor": "^1.1.2",
|
||||||
|
"react": "^16.8.6",
|
||||||
|
"react-dom": "^16.8.6",
|
||||||
|
"react-scripts": "3.0.1",
|
||||||
|
"react-table": "next",
|
||||||
|
"styled-components": "^4.3.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rescripts/cli": "^0.0.11",
|
||||||
|
"@rescripts/rescript-use-babel-config": "^0.0.8",
|
||||||
|
"@rescripts/rescript-use-eslint-config": "^0.0.9",
|
||||||
|
"babel-eslint": "10.0.1"
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
examples/filtering-with-useFilters/public/favicon.ico
Normal file
BIN
examples/filtering-with-useFilters/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
38
examples/filtering-with-useFilters/public/index.html
Normal file
38
examples/filtering-with-useFilters/public/index.html
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>React App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
15
examples/filtering-with-useFilters/public/manifest.json
Normal file
15
examples/filtering-with-useFilters/public/manifest.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
||||||
323
examples/filtering-with-useFilters/src/App.js
Normal file
323
examples/filtering-with-useFilters/src/App.js
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import { useTable, useFilters } from 'react-table'
|
||||||
|
// A great library for fuzzy filtering/sorting items
|
||||||
|
import matchSorter from 'match-sorter'
|
||||||
|
|
||||||
|
import makeData from './makeData'
|
||||||
|
|
||||||
|
const Styles = styled.div`
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-spacing: 0;
|
||||||
|
border: 1px solid black;
|
||||||
|
|
||||||
|
tr {
|
||||||
|
:last-child {
|
||||||
|
td {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
border-right: 1px solid black;
|
||||||
|
|
||||||
|
:last-child {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
// Define a default UI for filtering
|
||||||
|
function DefaultColumnFilter({ filterValue, setFilter }) {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
value={filterValue || ''}
|
||||||
|
onChange={e => {
|
||||||
|
setFilter(e.target.value || undefined) // Set undefined to remove the filter entirely
|
||||||
|
}}
|
||||||
|
placeholder="Search..."
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a custom filter UI for selecting
|
||||||
|
// a unique option from a list
|
||||||
|
function SelectColumnFilter({ filterValue, setFilter, preFilteredRows, id }) {
|
||||||
|
// Calculate the options for filtering
|
||||||
|
// using the preFilteredRows
|
||||||
|
const options = React.useMemo(() => {
|
||||||
|
const options = new Set()
|
||||||
|
preFilteredRows.forEach(row => {
|
||||||
|
options.add(row.values[id])
|
||||||
|
})
|
||||||
|
return [...options.values()]
|
||||||
|
}, [id, preFilteredRows])
|
||||||
|
|
||||||
|
// Render a multi-select box
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
value={filterValue}
|
||||||
|
onChange={e => {
|
||||||
|
setFilter(e.target.value || undefined)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="">All</option>
|
||||||
|
{options.map((option, i) => (
|
||||||
|
<option key={i} value={option}>
|
||||||
|
{option}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a custom filter UI that uses a
|
||||||
|
// slider to set the filter value between a column's
|
||||||
|
// min and max values
|
||||||
|
function SliderColumnFilter({ filterValue, setFilter, preFilteredRows, id }) {
|
||||||
|
// Calculate the min and max
|
||||||
|
// using the preFilteredRows
|
||||||
|
const [min, max] = React.useMemo(() => {
|
||||||
|
let min = 0
|
||||||
|
let max = 0
|
||||||
|
preFilteredRows.forEach(row => {
|
||||||
|
min = Math.min(row.values[id], min)
|
||||||
|
max = Math.max(row.values[id], max)
|
||||||
|
})
|
||||||
|
return [min, max]
|
||||||
|
}, [id, preFilteredRows])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
value={filterValue || min}
|
||||||
|
onChange={e => {
|
||||||
|
setFilter(parseInt(e.target.value, 10))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button onClick={() => setFilter(undefined)}>Off</button>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a custom UI for our 'between' or number range
|
||||||
|
// filter. It uses two number boxes and filters rows to
|
||||||
|
// ones that have values between the two
|
||||||
|
function NumberRangeColumnFilter({ filterValue = [], setFilter }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
display: flex;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
value={filterValue[0] || ''}
|
||||||
|
type="number"
|
||||||
|
onChange={e => {
|
||||||
|
const val = e.target.value
|
||||||
|
setFilter((old = []) => [val ? parseInt(val, 10) : undefined, old[1]])
|
||||||
|
}}
|
||||||
|
placeholder="Min"
|
||||||
|
css={`
|
||||||
|
width: 70px;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
to
|
||||||
|
<input
|
||||||
|
value={filterValue[1] || ''}
|
||||||
|
type="number"
|
||||||
|
onChange={e => {
|
||||||
|
const val = e.target.value
|
||||||
|
setFilter((old = []) => [old[0], val ? parseInt(val, 10) : undefined])
|
||||||
|
}}
|
||||||
|
placeholder="Max"
|
||||||
|
css={`
|
||||||
|
width: 70px;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fuzzyText(rows, id, filterValue) {
|
||||||
|
console.log(rows, filterValue)
|
||||||
|
return matchSorter(rows, filterValue, { keys: [row => row.values[id]] })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let the table remove the filter if the string is empty
|
||||||
|
fuzzyText.autoRemove = val => !val
|
||||||
|
|
||||||
|
// Our table component
|
||||||
|
function Table({ columns, data }) {
|
||||||
|
const filterTypes = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
// Add a new fuzzyText filter type.
|
||||||
|
fuzzyText,
|
||||||
|
// Or, override the default text filter to use
|
||||||
|
// "startWith"
|
||||||
|
text: (rows, id, filterValue) => {
|
||||||
|
return rows.filter(row => {
|
||||||
|
const rowValue = row.values[id]
|
||||||
|
return rowValue !== undefined
|
||||||
|
? String(rowValue)
|
||||||
|
.toLowerCase()
|
||||||
|
.startsWith(String(filterValue).toLowerCase())
|
||||||
|
: true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultColumn = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
// Let's set up our default Filter UI
|
||||||
|
Filter: DefaultColumnFilter,
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const { getTableProps, headerGroups, rows, prepareRow, state } = useTable(
|
||||||
|
{
|
||||||
|
columns,
|
||||||
|
data,
|
||||||
|
defaultColumn, // Be sure to pass the defaultColumn option
|
||||||
|
filterTypes,
|
||||||
|
},
|
||||||
|
useFilters // useFilters!
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<pre>
|
||||||
|
<code>{JSON.stringify(state[0].filters, null, 2)}</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<table {...getTableProps()}>
|
||||||
|
<thead>
|
||||||
|
{headerGroups.map(headerGroup => (
|
||||||
|
<tr {...headerGroup.getHeaderGroupProps()}>
|
||||||
|
{headerGroup.headers.map(column => (
|
||||||
|
<th {...column.getHeaderProps()}>
|
||||||
|
{column.render('Header')}
|
||||||
|
{/* Render the columns filter UI */}
|
||||||
|
<div>{column.canFilter ? column.render('Filter') : null}</div>
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{firstPageRows.map(
|
||||||
|
(row, i) =>
|
||||||
|
prepareRow(row) || (
|
||||||
|
<tr {...row.getRowProps()}>
|
||||||
|
{row.cells.map(cell => {
|
||||||
|
return (
|
||||||
|
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br />
|
||||||
|
<div>Showing the first 20 results of {rows.length} rows</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define a custom filter filter function!
|
||||||
|
function filterGreaterThan(rows, id, filterValue) {
|
||||||
|
return rows.filter(row => {
|
||||||
|
const rowValue = row.values[id]
|
||||||
|
return rowValue >= filterValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is an autoRemove method on the filter function that
|
||||||
|
// when given the new filter value and returns true, the filter
|
||||||
|
// will be automatically removed. Normally this is just an undefined
|
||||||
|
// check, but here, we want to remove the filter if it's not a number
|
||||||
|
filterGreaterThan.autoRemove = val => typeof val !== 'number'
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const columns = React.useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
Header: 'Name',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
Header: 'First Name',
|
||||||
|
accessor: 'firstName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Last Name',
|
||||||
|
accessor: 'lastName',
|
||||||
|
// Use our custom `fuzzyText` filter on this column
|
||||||
|
filter: 'fuzzyText',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Info',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
Header: 'Age',
|
||||||
|
accessor: 'age',
|
||||||
|
Filter: SliderColumnFilter,
|
||||||
|
filter: 'equals',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Visits',
|
||||||
|
accessor: 'visits',
|
||||||
|
Filter: NumberRangeColumnFilter,
|
||||||
|
filter: 'between',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Status',
|
||||||
|
accessor: 'status',
|
||||||
|
Filter: SelectColumnFilter,
|
||||||
|
filter: 'includes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Profile Progress',
|
||||||
|
accessor: 'progress',
|
||||||
|
Filter: SliderColumnFilter,
|
||||||
|
filter: filterGreaterThan,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const data = React.useMemo(() => makeData(10000), [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Styles>
|
||||||
|
<Table columns={columns} data={data} />
|
||||||
|
</Styles>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
||||||
9
examples/filtering-with-useFilters/src/App.test.js
Normal file
9
examples/filtering-with-useFilters/src/App.test.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
ReactDOM.render(<App />, div);
|
||||||
|
ReactDOM.unmountComponentAtNode(div);
|
||||||
|
});
|
||||||
13
examples/filtering-with-useFilters/src/index.css
Normal file
13
examples/filtering-with-useFilters/src/index.css
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
12
examples/filtering-with-useFilters/src/index.js
Normal file
12
examples/filtering-with-useFilters/src/index.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import './index.css';
|
||||||
|
import App from './App';
|
||||||
|
import * as serviceWorker from './serviceWorker';
|
||||||
|
|
||||||
|
ReactDOM.render(<App />, document.getElementById('root'));
|
||||||
|
|
||||||
|
// If you want your app to work offline and load faster, you can change
|
||||||
|
// unregister() to register() below. Note this comes with some pitfalls.
|
||||||
|
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||||
|
serviceWorker.unregister();
|
||||||
40
examples/filtering-with-useFilters/src/makeData.js
Normal file
40
examples/filtering-with-useFilters/src/makeData.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import namor from 'namor'
|
||||||
|
|
||||||
|
const range = len => {
|
||||||
|
const arr = []
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
arr.push(i)
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPerson = () => {
|
||||||
|
const statusChance = Math.random()
|
||||||
|
return {
|
||||||
|
firstName: namor.generate({ words: 1, numbers: 0 }),
|
||||||
|
lastName: namor.generate({ words: 1, numbers: 0 }),
|
||||||
|
age: Math.floor(Math.random() * 30),
|
||||||
|
visits: Math.floor(Math.random() * 100),
|
||||||
|
progress: Math.floor(Math.random() * 100),
|
||||||
|
status:
|
||||||
|
statusChance > 0.66
|
||||||
|
? 'relationship'
|
||||||
|
: statusChance > 0.33
|
||||||
|
? 'complicated'
|
||||||
|
: 'single',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function makeData(...lens) {
|
||||||
|
const makeDataLevel = (depth = 0) => {
|
||||||
|
const len = lens[depth]
|
||||||
|
return range(len).map(d => {
|
||||||
|
return {
|
||||||
|
...newPerson(),
|
||||||
|
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeDataLevel()
|
||||||
|
}
|
||||||
135
examples/filtering-with-useFilters/src/serviceWorker.js
Normal file
135
examples/filtering-with-useFilters/src/serviceWorker.js
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
// This optional code is used to register a service worker.
|
||||||
|
// register() is not called by default.
|
||||||
|
|
||||||
|
// This lets the app load faster on subsequent visits in production, and gives
|
||||||
|
// it offline capabilities. However, it also means that developers (and users)
|
||||||
|
// will only see deployed updates on subsequent visits to a page, after all the
|
||||||
|
// existing tabs open on the page have been closed, since previously cached
|
||||||
|
// resources are updated in the background.
|
||||||
|
|
||||||
|
// To learn more about the benefits of this model and instructions on how to
|
||||||
|
// opt-in, read https://bit.ly/CRA-PWA
|
||||||
|
|
||||||
|
const isLocalhost = Boolean(
|
||||||
|
window.location.hostname === 'localhost' ||
|
||||||
|
// [::1] is the IPv6 localhost address.
|
||||||
|
window.location.hostname === '[::1]' ||
|
||||||
|
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||||
|
window.location.hostname.match(
|
||||||
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export function register(config) {
|
||||||
|
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||||
|
// The URL constructor is available in all browsers that support SW.
|
||||||
|
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
||||||
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
|
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||||
|
// from what our page is served on. This might happen if a CDN is used to
|
||||||
|
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||||
|
|
||||||
|
if (isLocalhost) {
|
||||||
|
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||||
|
checkValidServiceWorker(swUrl, config);
|
||||||
|
|
||||||
|
// Add some additional logging to localhost, pointing developers to the
|
||||||
|
// service worker/PWA documentation.
|
||||||
|
navigator.serviceWorker.ready.then(() => {
|
||||||
|
console.log(
|
||||||
|
'This web app is being served cache-first by a service ' +
|
||||||
|
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Is not localhost. Just register service worker
|
||||||
|
registerValidSW(swUrl, config);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerValidSW(swUrl, config) {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register(swUrl)
|
||||||
|
.then(registration => {
|
||||||
|
registration.onupdatefound = () => {
|
||||||
|
const installingWorker = registration.installing;
|
||||||
|
if (installingWorker == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
installingWorker.onstatechange = () => {
|
||||||
|
if (installingWorker.state === 'installed') {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
// At this point, the updated precached content has been fetched,
|
||||||
|
// but the previous service worker will still serve the older
|
||||||
|
// content until all client tabs are closed.
|
||||||
|
console.log(
|
||||||
|
'New content is available and will be used when all ' +
|
||||||
|
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Execute callback
|
||||||
|
if (config && config.onUpdate) {
|
||||||
|
config.onUpdate(registration);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// At this point, everything has been precached.
|
||||||
|
// It's the perfect time to display a
|
||||||
|
// "Content is cached for offline use." message.
|
||||||
|
console.log('Content is cached for offline use.');
|
||||||
|
|
||||||
|
// Execute callback
|
||||||
|
if (config && config.onSuccess) {
|
||||||
|
config.onSuccess(registration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error during service worker registration:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkValidServiceWorker(swUrl, config) {
|
||||||
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
|
fetch(swUrl)
|
||||||
|
.then(response => {
|
||||||
|
// Ensure service worker exists, and that we really are getting a JS file.
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
if (
|
||||||
|
response.status === 404 ||
|
||||||
|
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||||
|
) {
|
||||||
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister().then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Service worker found. Proceed as normal.
|
||||||
|
registerValidSW(swUrl, config);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log(
|
||||||
|
'No internet connection found. App is running in offline mode.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregister() {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
10157
examples/filtering-with-useFilters/yarn.lock
Normal file
10157
examples/filtering-with-useFilters/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -46,7 +46,7 @@ function Table({ columns, data }) {
|
|||||||
<table {...getTableProps()}>
|
<table {...getTableProps()}>
|
||||||
<thead>
|
<thead>
|
||||||
{headerGroups.map(headerGroup => (
|
{headerGroups.map(headerGroup => (
|
||||||
<tr {...headerGroup.getRowProps()}>
|
<tr {...headerGroup.getHeaderGroupProps()}>
|
||||||
{headerGroup.headers.map(column => (
|
{headerGroup.headers.map(column => (
|
||||||
// Add the sorting props to control sorting. For this example
|
// Add the sorting props to control sorting. For this example
|
||||||
// we can add them into the header props
|
// we can add them into the header props
|
||||||
|
|||||||
@ -9,6 +9,8 @@ export const text = (rows, id, filterValue) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
text.autoRemove = val => !val
|
||||||
|
|
||||||
export const exactText = (rows, id, filterValue) => {
|
export const exactText = (rows, id, filterValue) => {
|
||||||
return rows.filter(row => {
|
return rows.filter(row => {
|
||||||
const rowValue = row.values[id]
|
const rowValue = row.values[id]
|
||||||
@ -18,6 +20,8 @@ export const exactText = (rows, id, filterValue) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exactText.autoRemove = val => !val
|
||||||
|
|
||||||
export const exactTextCase = (rows, id, filterValue) => {
|
export const exactTextCase = (rows, id, filterValue) => {
|
||||||
return rows.filter(row => {
|
return rows.filter(row => {
|
||||||
const rowValue = row.values[id]
|
const rowValue = row.values[id]
|
||||||
@ -27,6 +31,8 @@ export const exactTextCase = (rows, id, filterValue) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exactTextCase.autoRemove = val => !val
|
||||||
|
|
||||||
export const includes = (rows, id, filterValue) => {
|
export const includes = (rows, id, filterValue) => {
|
||||||
return rows.filter(row => {
|
return rows.filter(row => {
|
||||||
const rowValue = row.values[id]
|
const rowValue = row.values[id]
|
||||||
@ -34,6 +40,8 @@ export const includes = (rows, id, filterValue) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
includes.autoRemove = val => !val || !val.length
|
||||||
|
|
||||||
export const includesAll = (rows, id, filterValue) => {
|
export const includesAll = (rows, id, filterValue) => {
|
||||||
return rows.filter(row => {
|
return rows.filter(row => {
|
||||||
const rowValue = row.values[id]
|
const rowValue = row.values[id]
|
||||||
@ -41,6 +49,8 @@ export const includesAll = (rows, id, filterValue) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
includesAll.autoRemove = val => !val || !val.length
|
||||||
|
|
||||||
export const exact = (rows, id, filterValue) => {
|
export const exact = (rows, id, filterValue) => {
|
||||||
return rows.filter(row => {
|
return rows.filter(row => {
|
||||||
const rowValue = row.values[id]
|
const rowValue = row.values[id]
|
||||||
@ -48,9 +58,36 @@ export const exact = (rows, id, filterValue) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const between = (rows, id, filterValue) => {
|
exact.autoRemove = val => typeof val === 'undefined'
|
||||||
|
|
||||||
|
export const equals = (rows, id, filterValue) => {
|
||||||
return rows.filter(row => {
|
return rows.filter(row => {
|
||||||
const rowValue = row.values[id]
|
const rowValue = row.values[id]
|
||||||
return rowValue >= filterValue[0] && rowValue <= filterValue[1]
|
return rowValue == filterValue
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
equals.autoRemove = val => val == null
|
||||||
|
|
||||||
|
export const between = (rows, id, filterValue) => {
|
||||||
|
let [min, max] = filterValue || []
|
||||||
|
|
||||||
|
min = typeof min === 'number' ? min : -Infinity
|
||||||
|
max = typeof max === 'number' ? max : Infinity
|
||||||
|
|
||||||
|
if (min > max) {
|
||||||
|
const temp = min
|
||||||
|
min = max
|
||||||
|
max = temp
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows.filter(row => {
|
||||||
|
const rowValue = row.values[id]
|
||||||
|
return rowValue >= min && rowValue <= max
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
between.autoRemove = val =>
|
||||||
|
console.log(val) ||
|
||||||
|
!val ||
|
||||||
|
(typeof val[0] !== 'number' && typeof val[1] !== 'number')
|
||||||
|
|||||||
@ -57,6 +57,7 @@ export const useTable = (props, ...plugins) => {
|
|||||||
data,
|
data,
|
||||||
state,
|
state,
|
||||||
hooks,
|
hooks,
|
||||||
|
plugins,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (debug) console.time('hooks')
|
if (debug) console.time('hooks')
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useMemo } from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import { getFirstDefined, isFunction } from '../utils'
|
import { getFirstDefined, isFunction } from '../utils'
|
||||||
@ -33,40 +33,68 @@ export const useFilters = props => {
|
|||||||
rows,
|
rows,
|
||||||
columns,
|
columns,
|
||||||
filterTypes: userFilterTypes,
|
filterTypes: userFilterTypes,
|
||||||
defaultFilter = filterTypes.text,
|
|
||||||
manualFilters,
|
manualFilters,
|
||||||
disableFilters,
|
disableFilters,
|
||||||
hooks,
|
hooks,
|
||||||
state: [{ filters }, setState],
|
state: [{ filters }, setState],
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
const setFilter = (id, val) => {
|
const setFilter = (id, updater) => {
|
||||||
|
const column = columns.find(d => d.id === id)
|
||||||
|
|
||||||
|
const filterMethod = getFilterMethod(
|
||||||
|
column.filter,
|
||||||
|
userFilterTypes || {},
|
||||||
|
filterTypes
|
||||||
|
)
|
||||||
|
|
||||||
|
const { autoRemove } = filterMethod
|
||||||
|
|
||||||
return setState(old => {
|
return setState(old => {
|
||||||
if (typeof val === 'undefined') {
|
const newFilter =
|
||||||
const { [id]: prev, ...rest } = filters
|
typeof updater === 'function' ? updater(old.filters[id]) : updater
|
||||||
|
|
||||||
|
//
|
||||||
|
if (shouldAutoRemove(autoRemove, newFilter)) {
|
||||||
|
const { [id]: remove, ...newFilters } = old.filters
|
||||||
return {
|
return {
|
||||||
...old,
|
...old,
|
||||||
filters: {
|
filters: newFilters,
|
||||||
...rest,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...old,
|
...old,
|
||||||
filters: {
|
filters: {
|
||||||
...filters,
|
...old.filters,
|
||||||
[id]: val,
|
[id]: newFilter,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, actions.setFilter)
|
}, actions.setFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setAllFilters = filters => {
|
const setAllFilters = updater => {
|
||||||
return setState(old => {
|
return setState(old => {
|
||||||
|
const newFilters = typeof updater === 'function' ? updater(old) : updater
|
||||||
|
|
||||||
|
// Filter out undefined values
|
||||||
|
Object.keys(newFilters).forEach(id => {
|
||||||
|
const newFilter = newFilters[id]
|
||||||
|
const column = columns.find(d => d.id === id)
|
||||||
|
const autoRemove = getFilterMethod(
|
||||||
|
column.filter,
|
||||||
|
userFilterTypes || {},
|
||||||
|
filterTypes
|
||||||
|
)
|
||||||
|
|
||||||
|
if (shouldAutoRemove(autoRemove, newFilter)) {
|
||||||
|
delete newFilters[id]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...old,
|
...old,
|
||||||
filters,
|
filters: newFilters,
|
||||||
}
|
}
|
||||||
}, actions.setAllFilters)
|
}, actions.setAllFilters)
|
||||||
}
|
}
|
||||||
@ -95,7 +123,12 @@ export const useFilters = props => {
|
|||||||
return columns
|
return columns
|
||||||
})
|
})
|
||||||
|
|
||||||
const filteredRows = useMemo(() => {
|
// 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 = React.useMemo(() => {
|
||||||
if (manualFilters || !Object.keys(filters).length) {
|
if (manualFilters || !Object.keys(filters).length) {
|
||||||
return rows
|
return rows
|
||||||
}
|
}
|
||||||
@ -111,11 +144,15 @@ export const useFilters = props => {
|
|||||||
// Find the filters column
|
// Find the filters column
|
||||||
const column = columns.find(d => d.id === columnID)
|
const column = columns.find(d => d.id === columnID)
|
||||||
|
|
||||||
|
column.preFilteredRows = filteredSoFar
|
||||||
|
|
||||||
// Don't filter hidden columns or columns that have had their filters disabled
|
// Don't filter hidden columns or columns that have had their filters disabled
|
||||||
if (!column || column.filterable === false) {
|
if (!column || column.filterable === false) {
|
||||||
return filteredSoFar
|
return filteredSoFar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const columnFilter = column.filter || filterTypes.text
|
||||||
|
|
||||||
// Look up filter functions in this order:
|
// Look up filter functions in this order:
|
||||||
// column function
|
// column function
|
||||||
// column string lookup on user filters
|
// column string lookup on user filters
|
||||||
@ -123,13 +160,11 @@ export const useFilters = props => {
|
|||||||
// default function
|
// default function
|
||||||
// default string lookup on user filters
|
// default string lookup on user filters
|
||||||
// default string lookup on built-in filters
|
// default string lookup on built-in filters
|
||||||
const filterMethod =
|
const filterMethod = getFilterMethod(
|
||||||
isFunction(column.filter) ||
|
columnFilter,
|
||||||
(userFilterTypes || {})[column.filter] ||
|
userFilterTypes || {},
|
||||||
filterTypes[column.filter] ||
|
filterTypes
|
||||||
isFunction(defaultFilter) ||
|
)
|
||||||
(userFilterTypes || {})[defaultFilter] ||
|
|
||||||
filterTypes[defaultFilter]
|
|
||||||
|
|
||||||
if (!filterMethod) {
|
if (!filterMethod) {
|
||||||
console.warn(
|
console.warn(
|
||||||
@ -148,6 +183,9 @@ export const useFilters = props => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Apply the filter to any subRows
|
// Apply the filter to any subRows
|
||||||
|
// We technically could do this recursively in the above loop,
|
||||||
|
// but that would severely hinder the API for the user, since they
|
||||||
|
// would be required to do that recursion in some scenarios
|
||||||
filteredRows = filteredRows.map(row => {
|
filteredRows = filteredRows.map(row => {
|
||||||
if (!row.subRows) {
|
if (!row.subRows) {
|
||||||
return row
|
return row
|
||||||
@ -170,20 +208,21 @@ export const useFilters = props => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return filterRows(rows)
|
return filterRows(rows)
|
||||||
}, [
|
}, [manualFilters, filters, debug, rows, columns, userFilterTypes])
|
||||||
manualFilters,
|
|
||||||
filters,
|
|
||||||
debug,
|
|
||||||
rows,
|
|
||||||
columns,
|
|
||||||
userFilterTypes,
|
|
||||||
defaultFilter,
|
|
||||||
])
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...props,
|
...props,
|
||||||
setFilter,
|
setFilter,
|
||||||
setAllFilters,
|
setAllFilters,
|
||||||
|
preFilteredRows: rows,
|
||||||
rows: filteredRows,
|
rows: filteredRows,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldAutoRemove(autoRemove, value) {
|
||||||
|
return autoRemove ? autoRemove(value) : typeof value === 'undefined'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilterMethod(filter, userFilterTypes, filterTypes) {
|
||||||
|
return isFunction(filter) || userFilterTypes[filter] || filterTypes[filter]
|
||||||
|
}
|
||||||
|
|||||||
@ -53,8 +53,23 @@ export const useSortBy = props => {
|
|||||||
disableMultiSort,
|
disableMultiSort,
|
||||||
hooks,
|
hooks,
|
||||||
state: [{ sortBy }, setState],
|
state: [{ sortBy }, setState],
|
||||||
|
plugins,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
|
// If useSortBy should probably come after useFilters for
|
||||||
|
// the best performance, so let's hint to the user about that...
|
||||||
|
const pluginIndex = plugins.indexOf(useSortBy)
|
||||||
|
|
||||||
|
const useFiltersIndex = plugins.findIndex(
|
||||||
|
plugin => plugin.name === 'useFilters'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (useFiltersIndex > pluginIndex) {
|
||||||
|
console.warn(
|
||||||
|
'React Table: useSortBy should be placed before useFilters in your plugin list for better performance!'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
columns.forEach(column => {
|
columns.forEach(column => {
|
||||||
const { accessor, canSortBy } = column
|
const { accessor, canSortBy } = column
|
||||||
column.canSortBy = accessor
|
column.canSortBy = accessor
|
||||||
|
|||||||
@ -137,6 +137,8 @@ export function isFunction(a) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
function makePathArray(obj) {
|
function makePathArray(obj) {
|
||||||
return flattenDeep(obj)
|
return flattenDeep(obj)
|
||||||
.join('.')
|
.join('.')
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user