What is a "headless" UI 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. If you don't want to, then here's a quick explanation of why headless UI is important:
- Separation of Concerns - 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 presents countless edge cases for a library simply because it's a creative medium, and one where every developer does things differently. By not dictating UI concerns, React Table empowers the developer to design and extend the UI based on their unique use-case.
A Table Utility, not a Table Component
By acting as an ultra-smart table utility, React Table opens up the possibility for your tables to integrate into any existing theme, UI library or existing table markup. This also means that if you don't have an existing table component or table styles, React Table will help you learn to build the table markup and styles required to display great tables.
Installation
You can install React Table with NPM, Yarn, or a good ol'
<script>
via unpkg.com.
NPM
npm install react-table --save
or
yarn add react-table
React Table is compatible with React v16.8+ and works with ReactDOM and React Native.
Quick Start
At the heart of every React Table is the useTable hook and the table instance object that it returns. This instance object contains everything you'll need to build a table and interact with its 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!
In React Table, you the developer are responsible for rendering the UI (markup and styles) of your table, but don't let that intimidate you! Table UIs are fun and React Table exists to make the process much easier to wire up your own table UI.
To show you how this works. Let's start with a very basic table example.
Getting your data
When thinking about a table structure, you typically have rows which contain columns. While table configurations can get far more complex with nested columns, subrows, etc. for this basic quick start, we need to define some data that resembles this structure.
const data = React.useMemo(
() => [
{
col1: 'Hello',
col2: 'World',
},
{
col1: 'react-table',
col2: 'rocks',
},
{
col1: 'whatever',
col2: 'you want',
},
],
[]
)
It's important that we're using React.useMemo here to ensure that our data isn't recreated on every render. If we didn't use React.useMemo, the table would think it was receiving new data on every render and attempt to recalculate a lot of logic every single time. Not cool!
Define Columns
Now that we have some data, let's create a set of column definitions to pass into the useTable hook.
const columns = React.useMemo(
() => [
{
Header: 'Column 1',
accessor: 'col1', // accessor is the "key" in the data
},
{
Header: 'Column 2',
accessor: 'col2',
},
],
[]
)
Again, we're using React.useMemo so React Table doesn't recalculate the universe on every single render. Only when the memoized value actually changes!
Using the useTable hook
Now that you have some data and columns defined, we can pass those into the useTable hook to create a table instance.
const tableInstance = useTable({ columns, data })
useTable at the very least needs to be provided with an object containing the memoized columns and data.
Building a basic table UI
Nice! We have our table instance and we're almost there! However, we still don't have any table markup or styles to show, right?
Let's build a basic table structure using just HTML for now:
return (
<table>
<thead>
<tr>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
</tr>
</tbody>
</table>
)
Applying the table instance to markup
Now that we have our table structure, we can use the tableInstance to make it come to life!
const tableInstance = useTable({ columns, data })
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = tableInstance
return (
// apply the table props
<table {...getTableProps()}>
<thead>
{// Loop over the header rows
headerGroups.map(headerGroup => (
// Apply the header row props
<tr {...headerGroup.getHeaderGroupProps()}>
{// Loop over the headers in each row
headerGroup.headers.map(column => (
// Apply the header cell props
<th {...column.getHeaderProps()}>
{// Render the header
column.render('Header')}
</th>
))}
</tr>
))}
</thead>
{/* Apply the table body props */}
<tbody {...getTableBodyProps()}>
{// Loop over the table rows
rows.map(row => {
// Prepare the row for display
prepareRow(row)
return (
// Apply the row props
<tr {...row.getRowProps()}>
{// Loop over the rows cells
row.cells.map(cell => {
// Apply the cell props
return (
<td {...cell.getCellProps()}>
{// Render the cell contents
cell.render('Cell')}
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
)
Final Result
If we put all of this together, we should get a very basic (as well as temporarily ugly) table.
import { useTable } from 'react-table'
function App() {
const data = React.useMemo(
() => [
{
col1: 'Hello',
col2: 'World',
},
{
col1: 'react-table',
col2: 'rocks',
},
{
col1: 'whatever',
col2: 'you want',
},
],
[]
)
const columns = React.useMemo(
() => [
{
Header: 'Column 1',
accessor: 'col1', // accessor is the "key" in the data
},
{
Header: 'Column 2',
accessor: 'col2',
},
],
[]
)
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({ columns, data })
return (
<table {...getTableProps()} style={{ border: 'solid 1px blue' }}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th
{...column.getHeaderProps()}
style={{
borderBottom: 'solid 3px red',
background: 'aliceblue',
color: 'black',
fontWeight: 'bold',
}}
>
{column.render('Header')}
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map(row => {
prepareRow(row)
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return (
<td
{...cell.getCellProps()}
style={{
padding: '10px',
border: 'solid 1px gray',
background: 'papayawhip',
}}
>
{cell.render('Cell')}
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
)
}
Clearly this isn't ready to ship, but from a conceptual standpoint, you just learned the basics of using React Table!
API Overview
React Table uses React Hooks both internally and externally for almost all of its 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
- Plugin Hooks
Core Plugin Hooks
useTable
useFilters
useGlobalFilter
useSortBy
useGroupBy
useExpanded
usePagination
useRowSelect
useRowState
useColumnOrder
useResizeColumns
Layout Hooks
useBlockLayout
useAbsoluteLayout
useFlexLayout
- 3rd Party Plugin Hooks
LineUp-lite Hooks
Core Plugin Hooks
useStats
Column Hooks
useRowExpandColumn
useRowSelectColumn
useRowRankColumn
Numerous renderers for cells, groups, aggregations, and interactive summaries
Want your custom plugin hook listed here? Submit a PR!
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 in a final instance object that you can use to build your table UI and interact with the table's state.
const instance = useTable(
{
data: [...],
columns: [...],
},
useGroupBy,
useFilters,
useSortBy,
useExpanded,
usePagination
)
The stages of React Table and plugins
- useTable is called. A table instance is created.
- The instance.state is resolved from either a custom user state or an automatically generated one.
- A collection of plugin points is created at instance.hooks.
- Each plugin is given the opportunity to add hooks to instance.hook.
- As the useTable logic proceeds to run, each plugin hook type is used at a specific point in time with each individual hook function being executed the order it was registered.
- The final instance object is returned from useTable, which the developer then uses to construct their table.
This multi-stage process is the secret sauce that allows React Table plugin hooks to work together and compose nicely, while not stepping on each others toes.
To dive deeper into plugins, see Plugins and the Plugin Guide
Plugin Hook Order & Consistency
The order and usage of plugin hooks must follow The Laws of Hooks, 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.disableSortBy
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).
useTable
- Required
useTable is the root hook for React Table. To use it, pass it with an options object with at least a columns and data value, followed by any React Table compatible hooks you want to use.
Table Options
The following options are supported via the main options object passed to useTable(options)
- columns: Array<Column>
- Required
- Must be memoized
- The core columns configuration object for the entire table.
- Supports nested columns arrays via the column's columns key, eg. [{ Header: 'My Group', columns: [...] }]
- data: Array<any>
- Required
- Must be memoized
- The data array that you want to display on the table.
- initialState: Object
- Optional
- The initial state object for the table.
- Upon table initialization, this object is merged over the table's defaultState object (eg. {...defaultState, ...initialState}) that React Table and its hooks use to register default state to produce the final initial state object passed to the React.useState hook internally.
- initialState.hiddenColumns: Array<ColumnId: String>
- Optional
- The initial state object for hidden columns
- If a column's ID is contained in this array, it will be hidden
- To update hiddenColumns, pass a new array into setHiddenColumns which is supplied by useTable. Changing hiddenColumns directly won't cause the table to add the hidden columns back.
- autoResetHiddenColumns: Boolean
- Defaults to true
- When true, the hiddenColumns state will automatically reset if any of the following conditions are met:
columns is changed
- To disable, set to false
- stateReducer: Function(newState, action, prevState) => newState
- Optional
- With every action that is dispatched to the table's internal React.useReducer instance, this reducer is called and is allowed to modify the final state object for updating.
- It is passed the newState, action, and prevState and is expected to either return the newState or a modified version of the newState
- May also be used to "control" the state of the table, by overriding certain pieces of state regardless of the action.
- useControlledState: HookFunction(state) => controlledState
- Optional
- If you need to control part of the table state, this is the place to do it.
- This function is run on every single render, just like a hook and allows you to alter the final state of the table if necessary.
- You can use hooks inside of this function, but most of the time, we just suggest using React.useMemo to memoize your state overrides.
- See the FAQ "How can I manually control the table state?" for a an example.
- defaultColumn: Object
- Optional
- Defaults to {}
- The default column object for every column passed to React Table.
- Column-specific properties will override the properties in this object, eg. { ...defaultColumn, ...userColumn }
- This is particularly useful for adding global column properties. For instance, when using the useFilters plugin hook, add a default Filter renderer for every column, eg.{ Filter: MyDefaultFilterComponent }
- getSubRows: Function(row, relativeIndex) => Rows[]
- Optional
- Must be memoized
- Defaults to (row) => row.subRows || []
- Use this function to change how React Table detects subrows. You could even use this function to generate sub rows if you want.
- By default, it will attempt to return the subRows property on the row, or an empty array if that is not found.
- getRowId: Function(row, relativeIndex, ?parent) => string
- Use this function to change how React Table detects unique rows and also how it constructs each row's underlying id property.
- Optional
- Must be memoized
- Defaults to (row, relativeIndex, parent) => parent ? [parent.id, relativeIndex].join('.') : relativeIndex
Column Options