Published on 11.10.2019
When building React applications, tables are a common component to display data. They may look simple, but tables often need features like sorting, filtering, and pagination to enhance the user experience, especially when dealing with large datasets.
In this post, we’ll walk through how to build a reusable table component in React using Hooks. By the end of this tutorial, you’ll have a flexible table component that you can drop into any part of your app with minimal setup.
Let’s get started!
Why Build a Reusable Table Component?
A reusable table component saves time and effort by abstracting common functionality such as:
- Sorting: Allow users to sort data based on column values.
- Filtering: Let users filter rows based on search criteria.
- Pagination: Break up long datasets into smaller, paginated chunks for easier viewing.
This reusable table component will have these features, all while being configurable and flexible for various use cases.
Step 1: Setting Up the Project
Let’s start by creating a new React project if you don’t have one already:
npx create-react-app reusable-table
cd reusable-table
npm start
Once your app is set up, we can start by creating our Table component.
Step 2: Create the Table Component
First, let’s create a basic structure for the table. The table should accept the following props:
data
: An array of objects that holds the table data.columns
: An array that defines the columns to display in the table.
Create a new file Table.js
inside the src
directory.
// src/Table.js
import React from 'react';
const Table = ({ columns, data }) => {
return (
<table>
<thead>
<tr>
{columns.map((col) => (
<th key={col.accessor}>{col.Header}</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, idx) => (
<tr key={idx}>
{columns.map((col) => (
<td key={col.accessor}>{row[col.accessor]}</td>
))}
</tr>
))}
</tbody>
</table>
);
};
export default Table;
This basic table component will display the data in a tabular format using the columns
and data
props. The columns
prop defines the table headers, and the data
prop defines the rows.
Step 3: Add Sorting Functionality
Now, let’s add the ability to sort the table data when users click on the column headers. We can manage sorting state using the useState
hook.
Modify the Table.js
file to add sorting functionality:
// src/Table.js
import React, { useState } from 'react';
const Table = ({ columns, data }) => {
const [sortBy, setSortBy] = useState({ key: '', order: 'asc' });
const handleSort = (column) => {
const isAsc = sortBy.key === column.accessor && sortBy.order === 'asc';
setSortBy({ key: column.accessor, order: isAsc ? 'desc' : 'asc' });
};
const sortedData = React.useMemo(() => {
const { key, order } = sortBy;
if (!key) return data;
return [...data].sort((a, b) => {
const aValue = a[key];
const bValue = b[key];
if (order === 'asc') {
return aValue < bValue ? -1 : aValue > bValue ? 1 : 0;
} else {
return aValue < bValue ? 1 : aValue > bValue ? -1 : 0;
}
});
}, [data, sortBy]);
return (
<table>
<thead>
<tr>
{columns.map((col) => (
<th key={col.accessor} onClick={() => handleSort(col)}>
{col.Header}
{sortBy.key === col.accessor ? (sortBy.order === 'asc' ? ' 🔼' : ' 🔽') : ''}
</th>
))}
</tr>
</thead>
<tbody>
{sortedData.map((row, idx) => (
<tr key={idx}>
{columns.map((col) => (
<td key={col.accessor}>{row[col.accessor]}</td>
))}
</tr>
))}
</tbody>
</table>
);
};
export default Table;
Explanation:
- We added
useState
to manage the current sorting state (sortBy
), which keeps track of the column to sort by and the order (asc
ordesc
). - We added a
handleSort
function that toggles the sorting order when a column header is clicked. - The
sortedData
variable holds the sorted data, which is computed using theuseMemo
hook to ensure that sorting only happens when necessary (i.e., whendata
orsortBy
changes). - Each column header has an indicator (🔼 or 🔽) to show whether it’s sorted in ascending or descending order.
Step 4: Add Filtering Functionality
Next, we’ll implement a simple search filter that lets users search through the table’s rows based on a search term. We’ll use useState
to manage the search term.
Here’s the modified Table.js
with filtering:
// src/Table.js
import React, { useState } from 'react';
const Table = ({ columns, data }) => {
const [sortBy, setSortBy] = useState({ key: '', order: 'asc' });
const [filter, setFilter] = useState('');
const handleSort = (column) => {
const isAsc = sortBy.key === column.accessor && sortBy.order === 'asc';
setSortBy({ key: column.accessor, order: isAsc ? 'desc' : 'asc' });
};
const handleFilterChange = (e) => {
setFilter(e.target.value);
};
const filteredData = React.useMemo(() => {
return data.filter((row) => {
return columns.some((col) => {
return row[col.accessor].toString().toLowerCase().includes(filter.toLowerCase());
});
});
}, [data, filter, columns]);
const sortedData = React.useMemo(() => {
const { key, order } = sortBy;
if (!key) return filteredData;
return [...filteredData].sort((a, b) => {
const aValue = a[key];
const bValue = b[key];
if (order === 'asc') {
return aValue < bValue ? -1 : aValue > bValue ? 1 : 0;
} else {
return aValue < bValue ? 1 : aValue > bValue ? -1 : 0;
}
});
}, [filteredData, sortBy]);
return (
<div>
<input
type="text"
placeholder="Search..."
value={filter}
onChange={handleFilterChange}
/>
<table>
<thead>
<tr>
{columns.map((col) => (
<th key={col.accessor} onClick={() => handleSort(col)}>
{col.Header}
{sortBy.key === col.accessor ? (sortBy.order === 'asc' ? ' 🔼' : ' 🔽') : ''}
</th>
))}
</tr>
</thead>
<tbody>
{sortedData.map((row, idx) => (
<tr key={idx}>
{columns.map((col) => (
<td key={col.accessor}>{row[col.accessor]}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
};
export default Table;
Explanation:
- We added an input field for the search filter at the top of the table. The
filter
state manages the current search term. - The
filteredData
variable is computed usinguseMemo
, which filters the data based on the search term entered by the user. - The
sortedData
is then derived from the filtered data, ensuring that both sorting and filtering are applied in sequence.
Step 5: Add Pagination
Finally, let’s add basic pagination to the table to display a limited number of rows per page. We’ll use useState
to keep track of the current page and rows per page.
// src/Table.js
import React, { useState } from 'react';
const Table = ({ columns, data, rowsPerPage = 5 }) => {
const [sortBy, setSortBy] = useState({ key: '', order: 'asc' });
const [filter, setFilter] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const handleSort = (column) => {
const isAsc = sortBy.key === column.accessor && sortBy.order === 'asc';
setSortBy({ key: column.accessor, order: isAsc ? 'desc' : 'asc' });
};
const handleFilterChange = (e) => {
setFilter(e.target.value);
};
const handlePageChange = (page) => {
setCurrentPage(page);
};
const filteredData = React.useMemo(() => {
return data.filter((row) => {
return columns.some((col) => {
return row[col.accessor].toString().toLowerCase().includes(filter.toLowerCase());
});
});
}, [data, filter, columns]);
const sortedData = React.useMemo(() => {
const { key, order } = sortBy;
if (!key) return filteredData;
return [...filteredData].sort((a, b) => {
const aValue = a[key];
const bValue = b[key];
if (order === 'asc') {
return aValue < bValue ? -1 : aValue > bValue ? 1 : 0;
} else {
return aValue < bValue ? 1 : aValue > bValue ? -1 : 0;
}
});
}, [filteredData, sortBy]);
const paginatedData = React.useMemo(() => {
const startIdx = (currentPage - 1) * rowsPerPage;
const endIdx = startIdx + rowsPerPage;
return sortedData.slice(startIdx, endIdx);
}, [sortedData, currentPage, rowsPerPage]);
const totalPages = Math.ceil(sortedData.length / rowsPerPage);
return (
<div>
<input
type="text"
placeholder="Search..."
value={filter}
onChange={handleFilterChange}
/>
<table>
<thead>
<tr>
{columns.map((col) => (
<th key={col.accessor} onClick={() => handleSort(col)}>
{col.Header}
{sortBy.key === col.accessor ? (sortBy.order === 'asc' ? ' 🔼' : ' 🔽') : ''}
</th>
))}
</tr>
</thead>
<tbody>
{paginatedData.map((row, idx) => (
<tr key={idx}>
{columns.map((col) => (
<td key={col.accessor}>{row[col.accessor]}</td>
))}
</tr>
))}
</tbody>
</table>
<div>
<button onClick={() => handlePageChange(currentPage - 1)} disabled={currentPage === 1}>
Prev
</button>
<span>Page {currentPage} of {totalPages}</span>
<button onClick={() => handlePageChange(currentPage + 1)} disabled={currentPage === totalPages}>
Next
</button>
</div>
</div>
);
};
export default Table;
Explanation:
- We added
currentPage
to manage the current page state, androwsPerPage
to control how many rows are displayed per page. paginatedData
is computed based on the current page and the rows to display per page.- The pagination buttons allow the user to navigate between pages.
Conclusion
In this post, we’ve built a reusable table component in React using Hooks. The table component supports:
- Sorting by column.
- Filtering data based on a search term.
- Pagination to manage large datasets.
This component is flexible and reusable, allowing you to easily incorporate tables with sorting, filtering, and pagination in any React app.
By leveraging React’s Hooks, we’ve built a component that’s both modern and powerful, offering the benefits of functional components while keeping the logic clean and maintainable.
Happy coding!