mirror of
https://github.com/gosticks/react-table.git
synced 2025-10-16 11:55:36 +00:00
Documentation Checkpoint
This commit is contained in:
parent
272a99fcd8
commit
daff1f9ca3
812
README.md
812
README.md
@ -33,6 +33,10 @@ Hooks for building **lightweight, fast and extendable datagrids** for React
|
||||
- Extensible via hooks
|
||||
- <a href="https://medium.com/@tannerlinsley/why-i-wrote-react-table-and-the-problems-it-has-solved-for-nozzle-others-445c4e93d4a8#.axza4ixba" target="\_parent">"Why I wrote React Table and the problems it has solved for Nozzle.io"</a> by Tanner Linsley
|
||||
|
||||
## Demos
|
||||
|
||||
[React Table v7 Sandbox](https://codesandbox.io/s/m5lxzzpz69)
|
||||
|
||||
## Versions
|
||||
|
||||
- This documentation is for version 7.
|
||||
@ -126,37 +130,787 @@ Hooks for building **lightweight, fast and extendable datagrids** for React
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Example](#examples)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install React Table as a dependency
|
||||
|
||||
```bash
|
||||
# Yarn
|
||||
$ yarn add react-table
|
||||
|
||||
# NPM
|
||||
$ npm install react-table
|
||||
```
|
||||
|
||||
2. Import the `react-table` module
|
||||
|
||||
```javascript
|
||||
import { useReactTable } from 'react-table'
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
[React Table v7 Sandbox](https://codesandbox.io/s/m5lxzzpz69)
|
||||
|
||||
# Documentation
|
||||
|
||||
Documentation for v7 is coming soon. If you're looking for the [v6 documentation, click here](https://github.com/tannerlinsley/react-table/tree/v6)
|
||||
- [Installation](#installation)
|
||||
- [Concepts](#concepts)
|
||||
- [Setup](#setup)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
# Installation
|
||||
|
||||
Install React Table as a dependency using `npm` or `yarn`
|
||||
|
||||
```bash
|
||||
# NPM
|
||||
$ npm install react-table
|
||||
|
||||
# Yarn
|
||||
$ yarn add react-table
|
||||
```
|
||||
|
||||
To import React Table:
|
||||
|
||||
```js
|
||||
import {
|
||||
useTable,
|
||||
useColumns,
|
||||
useRows,
|
||||
useGroupBy,
|
||||
useFilters,
|
||||
useSortBy,
|
||||
useExpanded,
|
||||
usePagination,
|
||||
...
|
||||
} from 'react-table'
|
||||
```
|
||||
|
||||
# Concepts
|
||||
|
||||
## React Table is a "headless" library
|
||||
|
||||
React Table is a headless utility, which means out of the box, it doesn't render or supply any actual UI elements. You are in charge of utilizing the state and callbacks of the hooks provided by this library to render your own table markup. [Read this article to understand why React Table is built this way.](https://medium.com/merrickchristensen/headless-user-interface-components-565b0c0f2e18). If you don't want to, then here's a quick rundown anyway:
|
||||
|
||||
- Separation of Concern - Not that superficial kind you read about all the time. The real kind. React Table as a library honestly has no business being in charge of your UI. The look, feel, and overall experience of your table is what makes your app or product great. The less React Table gets in the way of that, the better!
|
||||
- Maintenance - By removing the massive (and seemingly endless) API surface area required to support every UI use-case, React Table can remain small, easy-to-use and simple to update/maintain.
|
||||
- Extensibility - UI present countless edge cases for a library simply because it's a creative medium, and one where every developer does things differently. By drawing a line between
|
||||
|
||||
## The React Table API
|
||||
|
||||
At the heart of every React Table is a table `instance` object. This object contains everything needed to build a table and interact with it's state. This includes, but is not limited to:
|
||||
|
||||
- Columns
|
||||
- Materialized Data
|
||||
- Sorting
|
||||
- Filtering
|
||||
- Grouping
|
||||
- Pagination
|
||||
- Expanded State
|
||||
- Any functionality provided by custom plugin hooks, too!
|
||||
|
||||
## Using Hooks for configuration, state and lifecycle
|
||||
|
||||
React Table uses React Hooks both internally and externally for 100% of it's configuration and lifecycle management. Naturally, this is what allows React Table to be headless and lightweight while still having a concise and simple API.
|
||||
|
||||
React Table is essentially a compatible collection of **custom React hooks**:
|
||||
|
||||
- The primary React Table hook
|
||||
- [`useTable`](#usetable)
|
||||
- Plugin Hooks
|
||||
- Required Plugin Hooks
|
||||
- [`useColumns`](#useColumns)
|
||||
- [`useRows`](#useRows)
|
||||
- Core Plugin Hooks
|
||||
- [`useTableState`](#useTableState)
|
||||
- [`useGroupBy`](#useGroupBy)
|
||||
- [`useFilters`](#useFilters)
|
||||
- [`useSortBy`](#useSortBy)
|
||||
- [`useExpanded`](#useExpanded)
|
||||
- [`usePagination`](#usePagination)
|
||||
- [`useTokenPagination`](#useTokenPagination)
|
||||
- Layout Plugin Hooks
|
||||
- [`useFlexLayout`](#useFlexLayout)
|
||||
- [`useAbsoluteLayout`](#useAbsoluteLayout) (coming soon!)
|
||||
- Custom Plugin Hooks
|
||||
- Get your custom plugin hook listed here!
|
||||
|
||||
### Hook Usage
|
||||
|
||||
`useTable` is the **primary** hook used to build a React Table. It serves as the starting point for **every option and every plugin hook** that React Table supports. The options passed into `useTable` are supplied to every plugin hook after it in the order they are supplied, eventually resulting a final `instance` object that you can use to build your table UI and interact with the table's state.
|
||||
|
||||
```js
|
||||
const instance = useTable(
|
||||
{
|
||||
data: [...],
|
||||
columns: [...],
|
||||
},
|
||||
useColumns,
|
||||
useRows,
|
||||
useGroupBy,
|
||||
useFilters,
|
||||
useSortBy,
|
||||
useExpanded,
|
||||
usePagination
|
||||
)
|
||||
```
|
||||
|
||||
### The stages of a React Table
|
||||
|
||||
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 one `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`
|
||||
|
||||
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.
|
||||
|
||||
### 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.
|
||||
|
||||
**Note: In the event that you want to programmatically enable or disable plugin hooks, most of them provide options to disable their functionality, eg. `options.disableSorting`**
|
||||
|
||||
### Option Memoization
|
||||
|
||||
React Table relies on memoization to determine when state and side effects should update or be calculated. This means that every option you pass to `useTable` should be memoized either via `React.useMemo` (for objects) or `React.useCallback` (for functions).
|
||||
|
||||
# React Table Hooks API
|
||||
|
||||
## `useTable`
|
||||
|
||||
- Required
|
||||
|
||||
`useTable` is the root hook for React Table. To use it, call it with an options object, followed by any React Table compatible hooks you want to use.
|
||||
|
||||
### Options
|
||||
|
||||
- `state: [stateObject, stateUpdater]`
|
||||
- Must be **memoized**
|
||||
- The state/updater pair for the table instance. You would want to override this if you plan on controlling or hoisting table state into your own code.
|
||||
- Defaults to using an internal `useTableState()` instance if not defined.
|
||||
- See [Controlling and Hoisting Table State](#controlling-and-hoistin-table-state)
|
||||
- `debug: Bool`
|
||||
- A flag to turn on debug mode.
|
||||
- Defaults to `false`
|
||||
|
||||
### Output
|
||||
|
||||
- `instance` - The instance object for the React Table
|
||||
|
||||
### Example
|
||||
|
||||
```js
|
||||
const instance = useTable(
|
||||
{
|
||||
// Options
|
||||
},
|
||||
useColumns,
|
||||
useRows,
|
||||
useGroupBy,
|
||||
useFilters,
|
||||
useSortBy,
|
||||
useExpanded,
|
||||
usePagination
|
||||
)
|
||||
```
|
||||
|
||||
## `useColumns`
|
||||
|
||||
- Required
|
||||
|
||||
`useColumns` is the hook responsible for supporting columns in React Table. It's required for every React Table.
|
||||
|
||||
### Options
|
||||
|
||||
- `columns: Array<Column>`
|
||||
- Required
|
||||
- Must be **memoized**
|
||||
- The core columns configuration object for the entire table.
|
||||
|
||||
### Output
|
||||
|
||||
The following values are provided to the table `instance`:
|
||||
|
||||
- `columns: Array<Column>`
|
||||
- A flat array of all final column objects computed from the original columns configuration option.
|
||||
- `headerGroups: Array<Array<Column>>`
|
||||
- An array of normalized header groups, each containing a flattened array of final column objects for that row.
|
||||
- `headers[] Array<Column>`
|
||||
- An array of nested final column objects, similar in structure to the original columns configuration option.
|
||||
|
||||
### Example
|
||||
|
||||
```js
|
||||
const myColumns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'Name',
|
||||
columns: [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
const { columns, headerGroups, headers } = useTable(
|
||||
{
|
||||
columns: myColumns,
|
||||
},
|
||||
useColumns
|
||||
)
|
||||
```
|
||||
|
||||
## `useRows`
|
||||
|
||||
- Required
|
||||
|
||||
`useColumns` is the hook responsible for supporting columns in React Table. It's required for every React Table.
|
||||
|
||||
### Options
|
||||
|
||||
- `data: Array<any>`
|
||||
- Required
|
||||
- Must be **memoized**
|
||||
- The data array that you want to display on the table.
|
||||
- `subRowsKey: String`
|
||||
- Required
|
||||
- Defaults to `subRows`
|
||||
- React Table will use this key when materializing the final row object. It also uses this key to infer sub-rows from the raw data.
|
||||
- See [Grouping and Aggregation](#grouping-and-aggregation) for more information
|
||||
|
||||
### Output
|
||||
|
||||
The following values are provided to the table `instance`:
|
||||
|
||||
- `rows: Array<Row>`
|
||||
- An array of rows **materialized** from the original `data` array and `columns` passed into the table options
|
||||
|
||||
### Example
|
||||
|
||||
```js
|
||||
const myColumns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'Name',
|
||||
columns: [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
const data = [
|
||||
{
|
||||
firstName: 'Tanner',
|
||||
lastName: 'Linsley',
|
||||
},
|
||||
{
|
||||
firstName: 'Shawn',
|
||||
lastName: 'Wang',
|
||||
},
|
||||
{
|
||||
firstName: 'Kent C.',
|
||||
lastName: 'Dodds',
|
||||
},
|
||||
{
|
||||
firstName: 'Ryan',
|
||||
lastName: 'Florence',
|
||||
},
|
||||
]
|
||||
|
||||
const { rows } = useTable(
|
||||
{
|
||||
columns: myColumns,
|
||||
data,
|
||||
},
|
||||
useColumns,
|
||||
useRows
|
||||
)
|
||||
```
|
||||
|
||||
## `useGroupBy`
|
||||
|
||||
- Optional
|
||||
|
||||
`useGroupBy` is the hook that implements **row grouping and aggregation**.
|
||||
|
||||
### Options
|
||||
|
||||
- `state[0].groupBy: Array<String>`
|
||||
- Must be **memoized**
|
||||
- An array of groupBy ID strings, controlling which columns are used to calculate row grouping and aggregation. This information is stored in state since the table is allowed to manipulate the groupBy through user interaction.
|
||||
- `groupByFn: Function`
|
||||
- Must be **memoized**
|
||||
- Defaults to [`defaultGroupByFn`](TODO)
|
||||
- This function is responsible for grouping rows based on the `state.groupBy` keys provided. It's very rare you would need to customize this function.
|
||||
- `manualGroupBy: Bool`
|
||||
- Enables groupBy detection and functionality, but does not automatically perform row grouping. Turn this on if you wish to implement your own row grouping outside of the table (eg. server-side or manual row grouping/nesting)
|
||||
- `disableGrouping: Bool`
|
||||
- Disables groupBy for the entire table.
|
||||
- `aggregations: Object<aggregationKey: aggregationFn>`
|
||||
- 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)
|
||||
|
||||
### Output
|
||||
|
||||
The following values are provided to the table `instance`:
|
||||
|
||||
- `rows: Array<Row>`
|
||||
- An array of **grouped and aggregated** rows.
|
||||
|
||||
### Example
|
||||
|
||||
```js
|
||||
const state = useTableState({ groupBy: ['firstName'] })
|
||||
|
||||
const aggregations = React.useMemo(() => ({
|
||||
customSum: (values, rows) => values.reduce((sum, next) => sum + next, 0),
|
||||
}))
|
||||
|
||||
const { rows } = useTable(
|
||||
{
|
||||
state, // state[0].groupBy === ['firstName']
|
||||
manualGroupBy: false,
|
||||
disableGrouping: false,
|
||||
aggregations,
|
||||
},
|
||||
useColumns,
|
||||
useRows,
|
||||
useGroupBy
|
||||
)
|
||||
```
|
||||
|
||||
## `useFilters`
|
||||
|
||||
- Optional
|
||||
|
||||
`useFilters` is the hook that implements **row filtering**.
|
||||
|
||||
### Options
|
||||
|
||||
- `state[0].filters: <Object<columnID: filterValue>`
|
||||
- Must be **memoized**
|
||||
- An object of 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.
|
||||
- `defaultFilter: String | Function`
|
||||
- If a **function** is passed, it must be **memoized**
|
||||
- Defaults to [`text`](TODO)
|
||||
- The function (or resolved function from the string) will be used as the default/fallback filter method for every column that has filtering enabled.
|
||||
- If a `string` is passd functionality, but does not automatically perform row filtering. Turn this on if you wish to ied, the function with that name located on the `filterTypes` option object will be used.
|
||||
- If a `function` is passed, it will be used.
|
||||
- For mor information on filter functions, see [Filtering](TODO)
|
||||
- `manualFilters: Bool`
|
||||
- Enables filter detection anmplement your own row filter outside of the table (eg. server-side or manual row grouping/nesting)
|
||||
- `disableFilters: Bool`
|
||||
- Disables filtering for every column in the entire table.
|
||||
- `filterTypes: Object<filterKey: filterType>`
|
||||
- Must be **memoized**
|
||||
- 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).
|
||||
- Read more about [Filter Types](TODO)
|
||||
|
||||
### Output
|
||||
|
||||
The following values are provided to the table `instance`:
|
||||
|
||||
setFilter,
|
||||
setAllFilters,
|
||||
|
||||
- `rows: Array<Row>`
|
||||
- An array of **filtered** rows.
|
||||
- `setFilter: Function(columnID, filterValue) => void`
|
||||
- An instance-level function used to update the filter value for a specific column.
|
||||
- `setAllFilters: Function(filtersObject) => void`
|
||||
- An instance-level function used to update the values for **all** filters on the table, all at once.
|
||||
|
||||
### Example
|
||||
|
||||
```js
|
||||
// A great library for fuzzy filtering/sorting items
|
||||
import matchSorter from 'match-sorter'
|
||||
|
||||
const state = useTableState({ filters: { firstName: 'tanner' } })
|
||||
|
||||
const filterTypes = React.useMemo(() => ({
|
||||
// Add a new fuzzyText filter type.
|
||||
fuzzyText: (rows, id, filterValue) => {
|
||||
return matchSorter(rows, filterValue, { keys: [row => row[id] })
|
||||
},
|
||||
// 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
|
||||
})
|
||||
}
|
||||
}), [matchSorter])
|
||||
|
||||
const { rows } = useTable(
|
||||
{
|
||||
// state[0].groupBy === ['firstName']
|
||||
state,
|
||||
// Override the default filter to be our new `fuzzyText` filter type
|
||||
defaultFilter: 'fuzzyText',
|
||||
manualFilters: false,
|
||||
disableFilters: false,
|
||||
// Pass our custom filter types
|
||||
filterTypes,
|
||||
},
|
||||
useColumns,
|
||||
useRows,
|
||||
useFilters
|
||||
)
|
||||
```
|
||||
|
||||
## `useSortBy`
|
||||
|
||||
- Optional
|
||||
|
||||
`useSortBy` is the hook that implements **row sorting**. It also support multi-sort (keyboard required).
|
||||
|
||||
### Options
|
||||
|
||||
- `state[0].filters: <Object<columnID: filterValue>`
|
||||
- Must be **memoized**
|
||||
- An object of 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.
|
||||
- `defaultFilter: String | Function`
|
||||
- If a **function** is passed, it must be **memoized**
|
||||
- Defaults to [`text`](TODO)
|
||||
- The function (or resolved function from the string) will be used as the default/fallback filter method for every column that has filtering enabled.
|
||||
- If a `string` is passd functionality, but does not automatically perform row filtering. Turn this on if you wish to ied, the function with that name located on the `filterTypes` option object will be used.
|
||||
- If a `function` is passed, it will be used.
|
||||
- For mor information on filter functions, see [Filtering](TODO)
|
||||
- `manualFilters: Bool`
|
||||
- Enables filter detection anmplement your own row filter outside of the table (eg. server-side or manual row grouping/nesting)
|
||||
- `disableFilters: Bool`
|
||||
- Disables filtering for every column in the entire table.
|
||||
- `filterTypes: Object<filterKey: filterType>`
|
||||
- Must be **memoized**
|
||||
- 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).
|
||||
- Read more about [Filter Types](TODO)
|
||||
|
||||
### Output
|
||||
|
||||
The following values are provided to the table `instance`:
|
||||
|
||||
setFilter,
|
||||
setAllFilters,
|
||||
|
||||
- `rows: Array<Row>`
|
||||
- An array of **filtered** rows.
|
||||
- `setFilter: Function(columnID, filterValue) => void`
|
||||
- An instance-level function used to update the filter value for a specific column.
|
||||
- `setAllFilters: Function(filtersObject) => void`
|
||||
- An instance-level function used to update the values for **all** filters on the table, all at once.
|
||||
|
||||
### Example
|
||||
|
||||
```js
|
||||
// A great library for fuzzy filtering/sorting items
|
||||
import matchSorter from 'match-sorter'
|
||||
|
||||
const state = useTableState({ filters: { firstName: 'tanner' } })
|
||||
|
||||
const filterTypes = React.useMemo(() => ({
|
||||
// Add a new fuzzyText filter type.
|
||||
fuzzyText: (rows, id, filterValue) => {
|
||||
return matchSorter(rows, filterValue, { keys: [row => row[id] })
|
||||
},
|
||||
// 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
|
||||
})
|
||||
}
|
||||
}), [matchSorter])
|
||||
|
||||
const { rows } = useTable(
|
||||
{
|
||||
// state[0].groupBy === ['firstName']
|
||||
state,
|
||||
// Override the default filter to be our new `fuzzyText` filter type
|
||||
defaultFilter: 'fuzzyText',
|
||||
manualFilters: false,
|
||||
disableFilters: false,
|
||||
// Pass our custom filter types
|
||||
filterTypes,
|
||||
},
|
||||
useColumns,
|
||||
useRows,
|
||||
useSortBy
|
||||
)
|
||||
```
|
||||
|
||||
# Guides
|
||||
|
||||
## Client Side Pagination
|
||||
|
||||
To add client side pagination, use the `usePagination` hook:
|
||||
|
||||
```diff
|
||||
// Import React
|
||||
import React from 'react'
|
||||
|
||||
// Import React Table
|
||||
import {
|
||||
useTable,
|
||||
useColumns,
|
||||
useRows,
|
||||
useGroupBy,
|
||||
useFilters,
|
||||
useSortBy,
|
||||
useExpanded,
|
||||
+ usePagination,
|
||||
} from 'react-table'
|
||||
|
||||
// Create a component to render your table
|
||||
function MyTable(props) {
|
||||
// Use the useTable hook to create your table configuration
|
||||
const instance = useTable(
|
||||
props,
|
||||
useColumns,
|
||||
useRows,
|
||||
useGroupBy,
|
||||
useFilters,
|
||||
useSortBy,
|
||||
useExpanded,
|
||||
+ usePagination,
|
||||
)
|
||||
|
||||
// Use the state and functions returned from useTable to build your UI
|
||||
const {
|
||||
getTableProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
getRowProps,
|
||||
prepareRow,
|
||||
+ pageOptions,
|
||||
+ page,
|
||||
+ state: [{ pageIndex, pageSize }],
|
||||
+ gotoPage,
|
||||
+ previousPage,
|
||||
+ nextPage,
|
||||
+ setPageSize,
|
||||
+ canPreviousPage,
|
||||
+ canNextPage,
|
||||
} = instance
|
||||
|
||||
// Render the UI for your table
|
||||
return (
|
||||
<div>
|
||||
<table {...getTableProps()}>
|
||||
...
|
||||
</table>
|
||||
+ <div>
|
||||
+ <button onClick={() => previousPage()} disabled={!canPreviousPage}>
|
||||
+ Previous Page
|
||||
+ </button>
|
||||
+ <button onClick={() => nextPage()} disabled={!canNextPage}>
|
||||
+ Next Page
|
||||
+ </button>
|
||||
+ <div>
|
||||
+ Page{' '}
|
||||
+ <em>
|
||||
+ {pageIndex + 1} of {pageOptions.length}
|
||||
+ </em>
|
||||
+ </div>
|
||||
+ <div>Go to page:</div>
|
||||
+ <input
|
||||
+ type="number"
|
||||
+ defaultValue={pageIndex + 1 || 1}
|
||||
+ onChange={e => {
|
||||
+ const page = e.target.value ? Number(e.target.value) - 1 : 0
|
||||
+ gotoPage(page)
|
||||
+ }}
|
||||
+ />
|
||||
+ <select
|
||||
+ value={pageSize}
|
||||
+ onChange={e => {
|
||||
+ setPageSize(Number(e.target.value))
|
||||
+ }}
|
||||
+ >
|
||||
+ {pageSizeOptions.map(pageSize => (
|
||||
+ <option key={pageSize} value={pageSize}>
|
||||
+ Show {pageSize}
|
||||
+ </option>
|
||||
+ ))}
|
||||
+ </select>
|
||||
+ </div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Server Side Pagination
|
||||
|
||||
To implement server-side pagination, use the `useTableState` and `usePagination` hooks:
|
||||
|
||||
```diff
|
||||
|
||||
```
|
||||
|
||||
# Setup
|
||||
|
||||
To begin using React Table you will need to start with a UI to render it. Below is a very basic component that should serve as a good starting point for most projects:
|
||||
|
||||
```js
|
||||
// Import React
|
||||
import React from 'react'
|
||||
|
||||
// Import React Table
|
||||
import {
|
||||
useTable,
|
||||
useColumns,
|
||||
useRows,
|
||||
useGroupBy,
|
||||
useFilters,
|
||||
useSortBy,
|
||||
useExpanded,
|
||||
usePagination,
|
||||
} from 'react-table'
|
||||
|
||||
// Create a component to render your table
|
||||
export default function MyTable(props) {
|
||||
// Use the useTable hook to create your table configuration
|
||||
const instance = useTable(
|
||||
props,
|
||||
useColumns,
|
||||
useRows,
|
||||
useGroupBy,
|
||||
useFilters,
|
||||
useSortBy,
|
||||
useExpanded,
|
||||
usePagination
|
||||
)
|
||||
|
||||
// Use the state and functions returned from useTable to build your UI
|
||||
const {
|
||||
getTableProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
getRowProps,
|
||||
pageOptions,
|
||||
page,
|
||||
state: [{ pageIndex, pageSize }],
|
||||
gotoPage,
|
||||
prepareRow,
|
||||
previousPage,
|
||||
nextPage,
|
||||
setPageSize,
|
||||
canPreviousPage,
|
||||
canNextPage,
|
||||
} = instance
|
||||
|
||||
// Render the UI for your table
|
||||
return (
|
||||
<div>
|
||||
<table {...getTableProps()}>
|
||||
<thead>
|
||||
{headerGroups.map(headerGroup => (
|
||||
<tr {...headerGroup.getRowProps()}>
|
||||
{headerGroup.headers.map(column => (
|
||||
<th {...column.getHeaderProps()}>{column.render('Header')}</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody>
|
||||
{page.map(
|
||||
(row, i) =>
|
||||
prepareRow(row) || (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map(cell => {
|
||||
return (
|
||||
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
<button onClick={() => previousPage()} disabled={!canPreviousPage}>
|
||||
Previous Page
|
||||
</button>
|
||||
<button onClick={() => nextPage()} disabled={!canNextPage}>
|
||||
Next Page
|
||||
</button>
|
||||
<div>
|
||||
Page{' '}
|
||||
<em>
|
||||
{pageIndex + 1} of {pageOptions.length}
|
||||
</em>
|
||||
</div>
|
||||
<div>Go to page:</div>
|
||||
<input
|
||||
type="number"
|
||||
defaultValue={pageIndex + 1 || 1}
|
||||
onChange={e => {
|
||||
const page = e.target.value ? Number(e.target.value) - 1 : 0
|
||||
gotoPage(page)
|
||||
}}
|
||||
/>
|
||||
<select
|
||||
value={pageSize}
|
||||
onChange={e => {
|
||||
setPageSize(Number(e.target.value))
|
||||
}}
|
||||
>
|
||||
{pageSizeOptions.map(pageSize => (
|
||||
<option key={pageSize} value={pageSize}>
|
||||
Show {pageSize}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You can then use your table like so:
|
||||
|
||||
```js
|
||||
|
||||
import MyTable from './MyTable'
|
||||
|
||||
function MyApp () {
|
||||
const columns = React.useMemo(() => [{
|
||||
Header: "Name",
|
||||
columns: [
|
||||
{
|
||||
Header: "First Name",
|
||||
accessor: "firstName",
|
||||
},
|
||||
{
|
||||
Header: "Last Name",
|
||||
accessor: "lastName",
|
||||
}
|
||||
]
|
||||
}], [])
|
||||
|
||||
const data = [{
|
||||
firstName: 'Tanner',
|
||||
lastName: 'Linsley'
|
||||
}, {
|
||||
firstName: 'Shawn',
|
||||
lastName: 'Wang'
|
||||
}, {
|
||||
firstName: 'Kent C.',
|
||||
lastName: 'Dodds'
|
||||
}, {
|
||||
firstName: 'Ryan',
|
||||
lastName: 'Florence'
|
||||
}]
|
||||
|
||||
return <MyTable columns={columns} data={data} />
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
56
src/filterTypes.js
Normal file
56
src/filterTypes.js
Normal file
@ -0,0 +1,56 @@
|
||||
export const text = (rows, id, filterValue) => {
|
||||
return rows.filter(row => {
|
||||
const rowValue = row.values[id]
|
||||
return rowValue !== undefined
|
||||
? String(rowValue)
|
||||
.toLowerCase()
|
||||
.includes(String(filterValue).toLowerCase())
|
||||
: true
|
||||
})
|
||||
}
|
||||
|
||||
export const exactText = (rows, id, filterValue) => {
|
||||
return rows.filter(row => {
|
||||
const rowValue = row.values[id]
|
||||
return rowValue !== undefined
|
||||
? String(rowValue).toLowerCase() === String(filterValue).toLowerCase()
|
||||
: true
|
||||
})
|
||||
}
|
||||
|
||||
export const exactTextCase = (rows, id, filterValue) => {
|
||||
return rows.filter(row => {
|
||||
const rowValue = row.values[id]
|
||||
return rowValue !== undefined
|
||||
? String(rowValue) === String(filterValue)
|
||||
: true
|
||||
})
|
||||
}
|
||||
|
||||
export const includes = (rows, id, filterValue) => {
|
||||
return rows.filter(row => {
|
||||
const rowValue = row.values[id]
|
||||
return filterValue.includes(rowValue)
|
||||
})
|
||||
}
|
||||
|
||||
export const includesAll = (rows, id, filterValue) => {
|
||||
return rows.filter(row => {
|
||||
const rowValue = row.values[id]
|
||||
return filterValue.every(val => rowValue.includes(val))
|
||||
})
|
||||
}
|
||||
|
||||
export const exact = (rows, id, filterValue) => {
|
||||
return rows.filter(row => {
|
||||
const rowValue = row.values[id]
|
||||
return rowValue === filterValue
|
||||
})
|
||||
}
|
||||
|
||||
export const between = (rows, id, filterValue) => {
|
||||
return rows.filter(row => {
|
||||
const rowValue = row.values[id]
|
||||
return rowValue >= filterValue[0] && rowValue <= filterValue[1]
|
||||
})
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
import { useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { defaultFilterFn, getFirstDefined } from '../utils'
|
||||
import { getFirstDefined } from '../utils'
|
||||
import * as filterTypes from '../filterTypes'
|
||||
import { addActions, actions } from '../actions'
|
||||
import { defaultState } from './useTableState'
|
||||
|
||||
@ -33,26 +34,14 @@ export const useFilters = props => {
|
||||
debug,
|
||||
rows,
|
||||
columns,
|
||||
filterFn = defaultFilterFn,
|
||||
filterTypes: userFilterTypes = {},
|
||||
defaultFilter = filterTypes.text,
|
||||
manualFilters,
|
||||
disableFilters,
|
||||
hooks,
|
||||
state: [{ filters }, setState],
|
||||
} = props
|
||||
|
||||
columns.forEach(column => {
|
||||
const { id, accessor, canFilter } = column
|
||||
column.canFilter = accessor
|
||||
? getFirstDefined(
|
||||
canFilter,
|
||||
disableFilters === true ? false : undefined,
|
||||
true
|
||||
)
|
||||
: false
|
||||
// Was going to add this to the filter hook
|
||||
column.filterValue = filters[id]
|
||||
})
|
||||
|
||||
const setFilter = (id, val) => {
|
||||
return setState(old => {
|
||||
if (typeof val === 'undefined') {
|
||||
@ -86,10 +75,25 @@ export const useFilters = props => {
|
||||
|
||||
hooks.columns.push(columns => {
|
||||
columns.forEach(column => {
|
||||
if (column.canFilter) {
|
||||
column.setFilter = val => setFilter(column.id, val)
|
||||
}
|
||||
const { id, accessor, canFilter } = column
|
||||
|
||||
// Determine if a column is filterable
|
||||
column.canFilter = accessor
|
||||
? getFirstDefined(
|
||||
canFilter,
|
||||
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 current filter value to the column for
|
||||
// convenience
|
||||
column.filterValue = filters[id]
|
||||
})
|
||||
|
||||
return columns
|
||||
})
|
||||
|
||||
@ -114,15 +118,33 @@ export const useFilters = props => {
|
||||
return filteredSoFar
|
||||
}
|
||||
|
||||
const filterMethod = column.filterMethod || filterFn
|
||||
// Look up filter functions in this order:
|
||||
// column function
|
||||
// column string lookup on user filters
|
||||
// column string lookup on built-in filters
|
||||
// default function
|
||||
// default string lookup on user filters
|
||||
// default string lookup on built-in filters
|
||||
const filterMethod =
|
||||
getFunctionalFilter(column.filter) ||
|
||||
userFilterTypes[column.filter] ||
|
||||
filterTypes[column.filter] ||
|
||||
getFunctionalFilter(defaultFilter) ||
|
||||
userFilterTypes[defaultFilter] ||
|
||||
filterTypes[defaultFilter]
|
||||
|
||||
// If 'filterAll' is set to true, pass the entire dataset to the filter method
|
||||
if (column.filterAll) {
|
||||
return filterMethod(filteredSoFar, columnID, filterValue, column)
|
||||
if (!filterMethod) {
|
||||
console.warn(
|
||||
`Could not find a valid 'column.filter' for column with the ID: ${
|
||||
column.id
|
||||
}.`
|
||||
)
|
||||
return filteredSoFar
|
||||
}
|
||||
return filteredSoFar.filter(row =>
|
||||
filterMethod(row, columnID, filterValue, column)
|
||||
)
|
||||
|
||||
// Pass the rows, id, filterValue and column to the filterMethod
|
||||
// to get the filtered rows back
|
||||
return filterMethod(filteredSoFar, columnID, filterValue, column)
|
||||
},
|
||||
rows
|
||||
)
|
||||
@ -150,7 +172,15 @@ export const useFilters = props => {
|
||||
}
|
||||
|
||||
return filterRows(rows)
|
||||
}, [manualFilters, filters, debug, rows, columns, filterFn])
|
||||
}, [
|
||||
manualFilters,
|
||||
filters,
|
||||
debug,
|
||||
rows,
|
||||
columns,
|
||||
userFilterTypes,
|
||||
defaultFilter,
|
||||
])
|
||||
|
||||
return {
|
||||
...props,
|
||||
@ -159,3 +189,9 @@ export const useFilters = props => {
|
||||
rows: filteredRows,
|
||||
}
|
||||
}
|
||||
|
||||
function getFunctionalFilter(filter) {
|
||||
if (typeof filter === 'function') {
|
||||
return filter
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,61 +151,4 @@ function getSizesForColumn(
|
||||
: getFirstDefined(width, minWidth, defaultFlex),
|
||||
maxWidth,
|
||||
}
|
||||
}
|
||||
|
||||
// const resetRefs = () => {
|
||||
// if (debug) console.info("resetRefs");
|
||||
// renderedCellInfoRef.current = {};
|
||||
// };
|
||||
|
||||
// const calculateAutoWidths = () => {
|
||||
// RAF(() => {
|
||||
// const newColumnMeasurements = {};
|
||||
// Object.values(renderedCellInfoRef.current).forEach(({ column, el }) => {
|
||||
// if (!el) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// let measurement = 0;
|
||||
|
||||
// const measureChildren = children => {
|
||||
// if (children) {
|
||||
// [].slice.call(children).forEach(child => {
|
||||
// measurement = Math.max(
|
||||
// measurement,
|
||||
// Math.ceil(child.offsetWidth) || 0
|
||||
// );
|
||||
// measureChildren(child.children);
|
||||
// });
|
||||
// }
|
||||
// return measurement;
|
||||
// };
|
||||
|
||||
// const parentDims = getElementDimensions(el);
|
||||
// measureChildren(el.children);
|
||||
|
||||
// newColumnMeasurements[column.id] = Math.max(
|
||||
// newColumnMeasurements[column.id] || 0,
|
||||
// measurement + parentDims.paddingLeft + parentDims.paddingRight
|
||||
// );
|
||||
// });
|
||||
|
||||
// const oldKeys = Object.keys(columnMeasurements);
|
||||
// const newKeys = Object.keys(newColumnMeasurements);
|
||||
|
||||
// const needsUpdate =
|
||||
// oldKeys.length !== newKeys.length ||
|
||||
// oldKeys.some(key => {
|
||||
// return columnMeasurements[key] !== newColumnMeasurements[key];
|
||||
// });
|
||||
|
||||
// if (needsUpdate) {
|
||||
// setState(old => {
|
||||
// return {
|
||||
// ...old,
|
||||
// columnMeasurements: newColumnMeasurements
|
||||
// };
|
||||
// }, actions.updateAutoWidth);
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
export const useSimpleLayout = props => {
|
||||
const {
|
||||
hooks: { columns: columnsHooks, getHeaderProps, getCellProps },
|
||||
} = props
|
||||
|
||||
columnsHooks.push(columns => {
|
||||
getHeaderProps.push(column => ({
|
||||
style: {
|
||||
boxSizing: 'border-box',
|
||||
width: column.width !== undefined ? `${column.width}px` : 'auto',
|
||||
},
|
||||
}))
|
||||
|
||||
getCellProps.push(cell => {
|
||||
return {
|
||||
style: {
|
||||
boxSizing: 'border-box',
|
||||
width:
|
||||
cell.column.width !== undefined ? `${cell.column.width}px` : 'auto',
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
return columns
|
||||
})
|
||||
|
||||
return props
|
||||
}
|
||||
@ -67,14 +67,6 @@ export function defaultGroupByFn(rows, grouper) {
|
||||
}, {})
|
||||
}
|
||||
|
||||
export function defaultFilterFn(row, id, value, column) {
|
||||
return row.values[id] !== undefined
|
||||
? String(row.values[id])
|
||||
.toLowerCase()
|
||||
.includes(String(value).toLowerCase())
|
||||
: true
|
||||
}
|
||||
|
||||
export function setBy(obj = {}, path, value) {
|
||||
const recurse = (obj, depth = 0) => {
|
||||
const key = path[depth]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user