In the world of web development, displaying data effectively is crucial. Whether it’s a list of products, user profiles, or financial transactions, presenting data in a clear, organized, and interactive way can significantly improve the user experience. This is where data grids, also known as data tables, come into play. They provide a structured format for displaying tabular data and often include features like sorting, filtering, pagination, and editing. In this tutorial, we’ll dive into how to use the React-Table npm package within a Next.js application to build powerful and user-friendly data grids.
Why React-Table?
React-Table is a popular and versatile library for creating data tables in React applications. It’s chosen by developers because of its flexibility, performance, and extensive feature set. Here’s why React-Table is a great choice:
- Performance: React-Table is optimized for performance, even with large datasets.
- Flexibility: It offers a high degree of customization, allowing you to tailor the table to your specific needs.
- Features: It includes built-in features like sorting, filtering, pagination, and column reordering.
- Community Support: React-Table has a large and active community, providing ample resources and support.
Setting Up Your Next.js Project
Before we start, make sure you have Node.js and npm (or yarn) installed on your system. If you don’t have a Next.js project set up, let’s create one:
npx create-next-app my-react-table-app
cd my-react-table-app
This will create a new Next.js project named my-react-table-app. Now, let’s install React-Table:
npm install react-table react-table-sticky
We’re also installing react-table-sticky to enable sticky headers and columns. This is optional but highly recommended for better user experience, especially with tables that have a lot of columns.
Creating a Basic Data Table
Let’s create a simple data table in our Next.js application. We’ll start by creating a basic component to display some sample data. Create a file named components/MyTable.js and paste the following code:
import React, { useMemo } from 'react'
import { useTable } from 'react-table'
function MyTable({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({
columns,
data,
})
return (
<table>
<thead>
{headerGroups.map(headerGroup => (
<tr>
{headerGroup.headers.map(column => (
<th>{column.render('Header')}</th>
))}
</tr>
))}
</thead>
<tbody>
{rows.map(row => {
prepareRow(row)
return (
<tr>
{row.cells.map(cell => {
return <td>{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
)
}
export default MyTable;
Let’s break down what’s happening in this code:
- Imports: We import
useTablefromreact-table, which is the core hook for creating tables. - useTable Hook: We call the
useTablehook, passing in thecolumnsanddataas props. This hook returns several properties that we’ll use to render the table. - getTableProps: This prop is applied to the
<table>element and contains the necessary attributes for React-Table to manage the table. - headerGroups: This is an array of header groups. We iterate over them to render the table headers.
- rows: This is an array of rows. We iterate over them to render the table rows.
- prepareRow: This function needs to be called for each row before rendering it.
- getRowProps, getHeaderProps, getCellProps: These are props that need to be spread on the corresponding HTML elements (
<tr>,<th>, and<td>) to enable React-Table’s functionality. - column.render(‘Header’) and cell.render(‘Cell’): These functions render the header and cell content, respectively.
Now, let’s create some sample data and define the columns. Open pages/index.js and update it with the following code:
import React, { useMemo } from 'react'
import MyTable from '../components/MyTable'
function Home() {
const columns = useMemo(
() => [
{ Header: 'Name', accessor: 'name' },
{ Header: 'Age', accessor: 'age' },
{ Header: 'City', accessor: 'city' },
],
[]
)
const data = useMemo(
() => [
{ name: 'John Doe', age: 30, city: 'New York' },
{ name: 'Jane Smith', age: 25, city: 'London' },
{ name: 'Peter Jones', age: 40, city: 'Paris' },
],
[]
)
return (
<div>
</div>
)
}
export default Home
Here’s what this code does:
- Import MyTable: We import the
MyTablecomponent we created earlier. - Define Columns: We use the
useMemohook to define the table columns. Each column object has aHeader(the column title) and anaccessor(the key in the data object to display). - Define Data: We use another
useMemohook to create sample data. This is an array of objects, where each object represents a row in the table. - Render MyTable: We render the
MyTablecomponent and pass thecolumnsanddataas props.
Now, run your Next.js development server with npm run dev or yarn dev and navigate to http://localhost:3000. You should see a basic data table with the sample data.
Adding Sorting
Sorting is a fundamental feature of any data table. React-Table makes it easy to add sorting to your table. First, update the MyTable.js file to include the sorting functionality:
import React, { useMemo } from 'react'
import { useTable, useSortBy } from 'react-table'
function MyTable({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable(
{
columns,
data,
},
useSortBy
)
return (
<table>
<thead>
{headerGroups.map(headerGroup => (
<tr>
{headerGroup.headers.map(column => (
<th> column.getHeaderProps().onClick} >
{column.render('Header')}
<span>
{column.isSorted
? column.isSortedDesc
? ' 🔽'
: ' 🔼'
: ''}
</span>
</th>
))}
</tr>
))}
</thead>
<tbody>
{rows.map(row => {
prepareRow(row)
return (
<tr>
{row.cells.map(cell => {
return <td>{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
)
}
export default MyTable;
Here’s what changed:
- Import useSortBy: We import
useSortByfromreact-table. - Add useSortBy to useTable: We pass
useSortByas the second argument to theuseTablehook. - Add Sorting Indicators: We added a conditional rendering of up and down arrows (
🔼and🔽) next to each header to indicate the sorting order.
Now, when you click on a column header, the table will sort the data based on that column. Clicking again will reverse the sort order.
Adding Filtering
Filtering allows users to narrow down the data displayed in the table. React-Table provides a flexible way to implement filtering. Let’s add a basic filter to our table. First, update the MyTable.js file:
import React, { useMemo } from 'react'
import { useTable, useSortBy, useFilters } from 'react-table'
function MyTable({ columns, data }) {
const filterTypes = React.useMemo(
() => ({
// Define your filter types here
// For example, a case-insensitive text filter:
text: (rows, ids, filterValue) => {
return rows.filter(row => {
return ids.some(id => {
const rowValue = row.values[id]
return String(rowValue)
.toLowerCase()
.includes(String(filterValue).toLowerCase())
})
})
},
}),
[]
)
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
setFilter,
} = useTable(
{
columns,
data,
filterTypes,
},
useSortBy,
useFilters
)
return (
<table>
<thead>
{headerGroups.map(headerGroup => (
<tr>
{headerGroup.headers.map(column => (
<th> column.getHeaderProps().onClick}>
{column.render('Header')}
<span>
{column.isSorted
? column.isSortedDesc
? ' 🔽'
: ' 🔼'
: ''}
</span>
<div>{column.canFilter ? column.render('Filter') : null}</div>
</th>
))}
</tr>
))}
</thead>
<tbody>
{rows.map(row => {
prepareRow(row)
return (
<tr>
{row.cells.map(cell => {
return <td>{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
)
}
export default MyTable;
Here’s what we changed:
- Import useFilters: We import
useFiltersfromreact-table. - Add useFilters to useTable: We pass
useFiltersas the third argument to theuseTablehook. - Add Filter Types: We define custom filter types inside the
useTablehook. - Render Filters: We added
{column.canFilter ? column.render('Filter') : null}to the header to render the filter components.
Next, we need to define the filters for our columns. Update pages/index.js:
import React, { useMemo } from 'react'
import MyTable from '../components/MyTable'
function Home() {
const columns = useMemo(
() => [
{
Header: 'Name',
accessor: 'name',
Filter: (props) => {
return (
{
props.setFilter(e.target.value)
}}
placeholder="Search Name"
/>
)
},
filter: 'text'
},
{
Header: 'Age',
accessor: 'age',
},
{
Header: 'City',
accessor: 'city',
Filter: (props) => {
return (
{
props.setFilter(e.target.value)
}}
placeholder="Search City"
/>
)
},
filter: 'text'
},
],
[]
)
const data = useMemo(
() => [
{ name: 'John Doe', age: 30, city: 'New York' },
{ name: 'Jane Smith', age: 25, city: 'London' },
{ name: 'Peter Jones', age: 40, city: 'Paris' },
],
[]
)
return (
<div>
</div>
)
}
export default Home
In this code, we’ve added a Filter property to the name and city columns. This defines an input field for filtering. The onChange event handler uses the setFilter function (provided by React-Table) to update the filter value. We also specify filter: 'text', which tells React-Table to use the custom text filter we defined in MyTable.js.
Now, when you run your application, you should see input fields under the ‘Name’ and ‘City’ headers. You can type in these fields to filter the table data.
Adding Pagination
Pagination is crucial for managing large datasets. It allows you to display data in manageable chunks, improving performance and user experience. Let’s add pagination to our table. First, update MyTable.js:
import React, { useMemo } from 'react'
import { useTable, useSortBy, useFilters, usePagination } from 'react-table'
function MyTable({ columns, data }) {
const filterTypes = React.useMemo(
() => ({
// Define your filter types here
// For example, a case-insensitive text filter:
text: (rows, ids, filterValue) => {
return rows.filter(row => {
return ids.some(id => {
const rowValue = row.values[id]
return String(rowValue)
.toLowerCase()
.includes(String(filterValue).toLowerCase())
})
})
},
}),
[]
)
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
setFilter,
page,
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state: { pageIndex, pageSize },
} = useTable(
{
columns,
data,
filterTypes,
initialState: { pageSize: 10, pageIndex: 0 },
},
useSortBy,
useFilters,
usePagination
)
return (
<div>
<table>
<thead>
{headerGroups.map(headerGroup => (
<tr>
{headerGroup.headers.map(column => (
<th> column.getHeaderProps().onClick}>
{column.render('Header')}
<span>
{column.isSorted
? column.isSortedDesc
? ' 🔽'
: ' 🔼'
: ''}
</span>
<div>{column.canFilter ? column.render('Filter') : null}</div>
</th>
))}
</tr>
))}
</thead>
<tbody>
{page.map(row => {
prepareRow(row)
return (
<tr>
{row.cells.map(cell => {
return <td>{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
<div>
<button> gotoPage(0)} disabled={!canPreviousPage}>
{'<<'}
</button>{' '}
<button> previousPage()} disabled={!canPreviousPage}>
{'<'}
</button>{' '}
<button> nextPage()} disabled={!canNextPage}>
{'>'}
</button>{' '}
<button> gotoPage(pageCount - 1)} disabled={!canNextPage}>
{'>>'}
</button>{' '}
<span>
Page{' '}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{' '}
</span>
<span>
| Go to page:
{
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
style={{ width: '100px' }}
/>
</span>{' '}
{
setPageSize(Number(e.target.value))
}}
>
{[10, 20, 30, 40, 50].map(pageSize => (
Show {pageSize}
))}
</div>
</div>
)
}
export default MyTable;
Here’s what we changed:
- Import usePagination: We import
usePaginationfromreact-table. - Add usePagination to useTable: We pass
usePaginationas the fourth argument to theuseTablehook. - Initial State: We added
initialState: { pageSize: 10, pageIndex: 0 }to theuseTableconfiguration to set the initial page size and page index. - Pagination Controls: We added the pagination controls (previous, next, page number input, and page size selection) to the component.
- Render Page: We changed the
rows.maptopage.mapto render only the rows for the current page.
Now, when you run your application, you’ll see pagination controls at the bottom of the table. The table will display only a subset of your data at a time, and you can navigate between pages.
Adding Sticky Headers and Columns
Sticky headers and columns are a great way to improve the user experience, especially when dealing with tables that have a large number of columns or rows. They allow users to always see the headers and important columns, even when scrolling. This is why we installed react-table-sticky earlier. Let’s incorporate this into our table component.
First, modify MyTable.js:
import React, { useMemo } from 'react'
import { useTable, useSortBy, useFilters, usePagination, useSticky } from 'react-table'
function MyTable({ columns, data }) {
const filterTypes = React.useMemo(
() => ({
// Define your filter types here
// For example, a case-insensitive text filter:
text: (rows, ids, filterValue) => {
return rows.filter(row => {
return ids.some(id => {
const rowValue = row.values[id]
return String(rowValue)
.toLowerCase()
.includes(String(filterValue).toLowerCase())
})
})
},
}),
[]
)
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
setFilter,
page,
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state: { pageIndex, pageSize },
} = useTable(
{
columns,
data,
filterTypes,
initialState: { pageSize: 10, pageIndex: 0 },
},
useSortBy,
useFilters,
usePagination,
useSticky
)
return (
<div>
<table style="{{">
<thead>
{headerGroups.map(headerGroup => (
<tr style="{{">
{headerGroup.headers.map(column => (
<th> column.getHeaderProps().onClick} style={{ borderBottom: 'solid 1px black', backgroundColor: 'white' }}>
{column.render('Header')}
<span>
{column.isSorted
? column.isSortedDesc
? ' 🔽'
: ' 🔼'
: ''}
</span>
<div>{column.canFilter ? column.render('Filter') : null}</div>
</th>
))}
</tr>
))}
</thead>
<tbody>
{page.map(row => {
prepareRow(row)
return (
<tr>
{row.cells.map(cell => {
return <td style="{{">{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
<div>
<button> gotoPage(0)} disabled={!canPreviousPage}>
{'<<'}
</button>{' '}
<button> previousPage()} disabled={!canPreviousPage}>
{'<'}
</button>{' '}
<button> nextPage()} disabled={!canNextPage}>
{'>'}
</button>{' '}
<button> gotoPage(pageCount - 1)} disabled={!canNextPage}>
{'>>'}
</button>{' '}
<span>
Page{' '}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{' '}
</span>
<span>
| Go to page:
{
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
style={{ width: '100px' }}
/>
</span>{' '}
{
setPageSize(Number(e.target.value))
}}
>
{[10, 20, 30, 40, 50].map(pageSize => (
Show {pageSize}
))}
</div>
</div>
)
}
export default MyTable;
Here’s what we changed:
- Import useSticky: We import
useStickyfromreact-table. - Add useSticky to useTable: We pass
useStickyas the fifth argument to theuseTablehook. - Add Styles: We added inline styles to the
<table>,<tr>(header row), and<th>elements to enable sticky headers.
Now, when you run your application, the table headers will remain fixed at the top of the table as you scroll vertically. To make the first few columns sticky as well, you would need to add similar styling to the first few <td> elements within the table body, along with setting appropriate left values to position the sticky columns correctly. This would involve inspecting the computed styles of the table and adjusting the left values accordingly.
Common Mistakes and How to Fix Them
While working with React-Table, you might encounter a few common issues. Here’s a look at some of them and how to resolve them:
- Incorrect Data Format: React-Table expects data in a specific format (an array of objects). Ensure your data is structured correctly. If you’re fetching data from an API, make sure to transform it into the expected format.
- Missing Props: Always make sure you’re passing the necessary props to the React-Table components. This includes
columnsanddata. - Incorrect use of Hooks: Ensure you are using the various hooks provided by React-Table (
useTable,useSortBy,useFilters, etc.) correctly. Refer to the documentation for proper usage. - Styling Issues: React-Table doesn’t provide default styling. You’ll need to style the table components yourself. Make sure you’re applying styles correctly and that they don’t conflict with other styles in your application.
- Performance Issues with Large Datasets: For very large datasets, consider using server-side pagination and sorting to improve performance. React-Table supports this, but it requires more complex implementation.
- Filter Not Working: If your filters aren’t working, double-check that you’ve defined the
Filtercomponents correctly in your column definitions and that thefilterproperty is set correctly. Also, make sure that you’ve included the filter type in the useTable configuration.
Key Takeaways
This tutorial covered the basics of building data tables with React-Table in a Next.js application. We learned how to:
- Set up a basic React-Table component.
- Add sorting, filtering, and pagination.
- Implement sticky headers and columns.
- Avoid common pitfalls.
React-Table is a powerful tool for displaying and managing data in your web applications. By mastering its core concepts, you can create highly functional and user-friendly data tables tailored to your specific needs. Remember to consult the official React-Table documentation for more advanced features and customization options.
FAQ
Here are some frequently asked questions about React-Table:
- Can I customize the table styling? Yes, React-Table provides a lot of flexibility for styling. You can use CSS, CSS-in-JS libraries, or any other styling method you prefer to customize the appearance of your tables.
- Does React-Table support server-side data? Yes, React-Table supports server-side pagination, sorting, and filtering. You’ll need to handle the data fetching and processing on your server and provide the results to React-Table.
- How do I handle column resizing? React-Table doesn’t have built-in column resizing. You can use third-party libraries or implement your own column resizing functionality using the
useResizeColumnshook. - Can I add custom components to table cells? Yes, you can render any React component inside the table cells by using the
Cellproperty in your column definitions. - Is React-Table accessible? React-Table provides some accessibility features, but you may need to add additional ARIA attributes and keyboard navigation to fully ensure accessibility.
As you continue to work with React-Table, you’ll discover even more advanced features and customization options. Don’t hesitate to experiment and explore the library’s capabilities. Remember that the key to mastering any library is practice. Build tables with different data, try out various features, and don’t be afraid to consult the documentation and community resources. Data tables are a common requirement in many web applications, and mastering React-Table will be a valuable skill in your front-end development toolkit. You’ll find that with a little practice, creating complex and interactive data grids becomes a manageable and enjoyable task, enhancing the user experience and improving the way data is presented in your applications.
