React Logoreact-table

react-table is a powerful, lightweight, and extensible utility for building complex tables in React applications. Unlike many other table libraries, react-table is 'headless', meaning it doesn't render any UI elements itself. Instead, it provides a set of hooks and functions that allow developers to control the table's state and logic (like sorting, filtering, pagination, grouping, row selection, etc.) while giving them complete freedom over the table's visual presentation. This headless approach makes react-table incredibly flexible and customizable, as you can integrate it seamlessly with any UI library or custom styling.

Key characteristics and features:

1. Headless UI: It focuses solely on table logic and state management, not on rendering the actual HTML/CSS. This allows developers to use their own components and styles for rows, cells, headers, and pagination controls.
2. Hooks-based API: Built entirely with React Hooks (specifically `useTable`, `useSortBy`, `usePagination`, `useFilters`, `useGroupBy`, `useExpanded`, `useRowSelect`, etc.), making it idiomatic React and easy to integrate into functional components.
3. Lightweight: Minimal bundle size because it doesn't include any UI components or default styling.
4. Extensible: Its modular design allows developers to pick and choose the features they need. You can add plugins/hooks for various functionalities without bloat.
5. Performance: Designed for performance, especially with large datasets, by using memoization and efficient state updates.
6. Virtualization Compatible: Its headless nature makes it easy to integrate with virtualization libraries (like `react-window` or `react-virtualized`) for rendering extremely large tables efficiently.
7. Modern: The most popular version (v7) is built around React Hooks, and its successor (TanStack Table, formerly react-table v8) continues this powerful, headless, framework-agnostic approach.

To use react-table, you typically define your column structure and provide your data. Then, you pass these to the `useTable` hook (along with any other feature-specific hooks like `useSortBy` or `usePagination`). The hook returns an object with properties and functions that you use to render your table, such as `getTableProps`, `getTableBodyProps`, `headerGroups`, `rows`, `prepareRow`, and state variables for sorting, pagination, etc.

Example Code

import React from 'react';
import { useTable, useSortBy, usePagination } from 'react-table';

const App = () => {
  const data = React.useMemo(
    () => [
      { id: 1, firstName: 'John', lastName: 'Doe', age: 30, city: 'New York' },
      { id: 2, firstName: 'Jane', lastName: 'Smith', age: 24, city: 'London' },
      { id: 3, firstName: 'Peter', lastName: 'Jones', age: 35, city: 'Paris' },
      { id: 4, firstName: 'Alice', lastName: 'Williams', age: 28, city: 'Sydney' },
      { id: 5, firstName: 'Robert', lastName: 'Brown', age: 42, city: 'Berlin' },
      { id: 6, firstName: 'Emily', lastName: 'Davis', age: 29, city: 'Tokyo' },
      { id: 7, firstName: 'Michael', lastName: 'Miller', age: 31, city: 'Rome' },
      { id: 8, firstName: 'Sarah', lastName: 'Wilson', age: 22, city: 'Madrid' },
      { id: 9, firstName: 'David', lastName: 'Moore', age: 38, city: 'Dublin' },
      { id: 10, firstName: 'Laura', lastName: 'Taylor', age: 27, city: 'Amsterdam' },
      { id: 11, firstName: 'James', lastName: 'Anderson', age: 45, city: 'Oslo' },
      { id: 12, firstName: 'Olivia', lastName: 'Thomas', age: 23, city: 'Helsinki' }
    ],
    []
  );

  const columns = React.useMemo(
    () => [
      {
        Header: 'ID',
        accessor: 'id', // accessor is the "key" in the data
      },
      {
        Header: 'First Name',
        accessor: 'firstName',
      },
      {
        Header: 'Last Name',
        accessor: 'lastName',
      },
      {
        Header: 'Age',
        accessor: 'age',
      },
      {
        Header: 'City',
        accessor: 'city',
      },
    ],
    []
  );

  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    prepareRow,
    page, // Instead of 'rows', we use 'page' for pagination
    canPreviousPage,
    canNextPage,
    pageOptions,
    pageCount,
    gotoPage,
    nextPage,
    previousPage,
    setPageSize,
    state: { pageIndex, pageSize },
  } = useTable(
    {
      columns,
      data,
      initialState: { pageIndex: 0, pageSize: 5 }, // Start on the first page with 5 items per page
    },
    useSortBy,
    usePagination
  );

  return (
    <div style={{ padding: '20px' }}>
      <h1>My Sortable & Paginated Data Table</h1>
      <table {...getTableProps()} style={{ borderCollapse: 'collapse', width: '100%', marginBottom: '20px' }}>
        <thead>
          {headerGroups.map(headerGroup => (
            <tr {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.map(column => (
                // Add the sorting props to control sorting
                <th
                  {...column.getHeaderProps(column.getSortByToggleProps())}
                  style={{
                    borderBottom: 'solid 3px red',
                    background: 'aliceblue',
                    color: 'black',
                    fontWeight: 'bold',
                    padding: '8px',
                    textAlign: 'left',
                    cursor: 'pointer'
                  }}
                >
                  {column.render('Header')}
                  {/* Add a sort direction indicator */}
                  <span>
                    {column.isSorted
                      ? column.isSortedDesc
                        ? ' 🔽'
                        : ' 🔼'
                      : ''}
                  </span>
                </th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody {...getTableBodyProps()}>
          {page.map(row => {
            prepareRow(row);
            return (
              <tr {...row.getRowProps()} style={{ borderBottom: 'solid 1px gray' }}>
                {row.cells.map(cell => {
                  return (
                    <td
                      {...cell.getCellProps()}
                      style={{
                        padding: '8px',
                        border: 'solid 1px gray',
                        background: 'papayawhip',
                      }}
                    >
                      {cell.render('Cell')}
                    </td>
                  );
                })}
              </tr>
            );
          })}
        </tbody>
      </table>

      {/* Pagination controls */}
      <div className="pagination" style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
        <button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
          {'<<'}
        </button>{' '}
        <button onClick={() => previousPage()} disabled={!canPreviousPage}>
          {'<'}
        </button>{' '}
        <button onClick={() => nextPage()} disabled={!canNextPage}>
          {'>'}
        </button>{' '}
        <button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
          {'>>'}
        </button>{' '}
        <span>
          Page{' '}
          <strong>
            {pageIndex + 1} of {pageOptions.length}
          </strong>{' '}
        </span>
        <span>
          | Go to page:{' '}
          <input
            type="number"
            defaultValue={pageIndex + 1}
            onChange={e => {
              const page = e.target.value ? Number(e.target.value) - 1 : 0;
              gotoPage(page);
            }}
            style={{ width: '50px' }}
          />
        </span>{' '}
        <select
          value={pageSize}
          onChange={e => {
            setPageSize(Number(e.target.value));
          }}
        >
          {[5, 10, 20, 50].map(pageSizeOption => (
            <option key={pageSizeOption} value={pageSizeOption}>
              Show {pageSizeOption}
            </option>
          ))}
        </select>
      </div>
    </div>
  );
};

export default App;