CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-table

Hooks for building lightweight, fast and extendable datagrids for React

Pending
Overview
Eval results
Files

sorting.mddocs/

Data Sorting

React Table provides comprehensive sorting capabilities including single-column sorting, multi-column sorting, and customizable sort types. The sorting system supports various data types and allows for complete customization of sorting behavior.

Capabilities

Column Sorting (useSortBy)

Enables single and multi-column sorting with customizable sort algorithms and intuitive user interactions.

/**
 * Adds sorting capabilities to the table
 * @param hooks - Hook registration object
 */
function useSortBy(hooks: Hooks): void;

interface SortByInstance {
  /** Set the complete sort configuration */
  setSortBy: (sortBy: SortingRule[]) => void;
  /** Toggle sorting for a specific column */
  toggleSortBy: (columnId: string, desc?: boolean, multi?: boolean) => void;
  /** Clear all sorting */
  resetSortBy: () => void;
  /** Rows before sorting was applied */
  preSortedRows: Row[];
  /** Rows after sorting */
  sortedRows: Row[];
}

interface SortByColumnInstance {
  /** Whether this column can be sorted */
  canSort: boolean;
  /** Whether this column is currently sorted */
  isSorted: boolean;
  /** Sort direction (true = descending, false = ascending) */
  isSortedDesc?: boolean;
  /** Position in multi-sort order (0-based) */
  sortedIndex?: number;
  /** Toggle sorting for this column */
  toggleSortBy: (desc?: boolean, multi?: boolean) => void;
  /** Clear sorting for this column */
  clearSortBy: () => void;
  /** Get props for sort toggle UI element */
  getSortByToggleProps: PropGetter;
}

interface SortingRule {
  /** Column ID to sort by */
  id: string;
  /** Sort direction (true = descending, false = ascending) */
  desc: boolean;
}

Configuration Options:

interface SortByOptions {
  /** Disable automatic sorting (handle sorting externally) */
  manualSortBy?: boolean;
  /** Disable all sorting */
  disableSortBy?: boolean;
  /** Disable multi-column sorting */
  disableMultiSort?: boolean;
  /** Function to detect multi-sort events (default: shift key) */
  isMultiSortEvent?: (event: Event) => boolean;
  /** Maximum number of columns that can be sorted simultaneously */
  maxMultiSortColCount?: number;
  /** Disable removal of sorting when clicking a sorted column */
  disableSortRemove?: boolean;
  /** Disable removal in multi-sort mode */
  disableMultiRemove?: boolean;
  /** Custom sorting function */
  orderByFn?: (rows: Row[], sortBy: SortingRule[], sortTypes: SortTypes) => Row[];
  /** Custom sort type definitions */
  sortTypes?: Record<string, SortFunction>;
  /** Auto-reset sorting when data changes */
  autoResetSortBy?: boolean;
  /** Initial sort state */
  initialState?: {
    sortBy?: SortingRule[];
  };
}

interface SortFunction {
  (rowA: Row, rowB: Row, columnId: string, desc: boolean): number;
}

Usage Example:

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

function SortableTable({ columns, data }) {
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
    state: { sortBy },
  } = useTable(
    {
      columns,
      data,
      initialState: {
        sortBy: [
          { id: 'name', desc: false }, // Sort by name ascending initially
        ],
      },
    },
    useSortBy
  );

  return (
    <div>
      {/* Sort Status Display */}
      <div>
        {sortBy.length > 0 && (
          <div>
            Sorted by: {sortBy.map((sort, index) => (
              <span key={sort.id}>
                {index > 0 && ', '}
                {sort.id} ({sort.desc ? 'desc' : 'asc'})
              </span>
            ))}
          </div>
        )}
      </div>

      <table {...getTableProps()}>
        <thead>
          {headerGroups.map(headerGroup => (
            <tr {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.map(column => (
                <th {...column.getHeaderProps(column.getSortByToggleProps())}>
                  {column.render('Header')}
                  {/* Sort indicator */}
                  <span>
                    {column.isSorted ? (
                      column.isSortedDesc ? (
                        ' ↓'
                      ) : (
                        ' ↑'
                      )
                    ) : (
                      column.canSort ? ' ↕' : ''
                    )}
                  </span>
                  {/* Multi-sort index */}
                  {column.isSorted && sortBy.length > 1 && (
                    <span>{column.sortedIndex + 1}</span>
                  )}
                </th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody {...getTableBodyProps()}>
          {rows.map(row => {
            prepareRow(row);
            return (
              <tr {...row.getRowProps()}>
                {row.cells.map(cell => (
                  <td {...cell.getCellProps()}>
                    {cell.render('Cell')}
                  </td>
                ))}
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

Built-in Sort Types

React Table includes several built-in sort functions for different data types.

interface SortTypes {
  /** Mixed alphanumeric sorting (default) */
  alphanumeric: SortFunction;
  /** Date/time sorting */
  datetime: SortFunction;
  /** Basic comparison sorting */
  basic: SortFunction;
  /** String sorting with case handling */
  string: SortFunction;
  /** Numeric sorting with string cleaning */
  number: SortFunction;
}

Sort Type Examples:

const columns = [
  {
    Header: 'Product Name',
    accessor: 'name',
    sortType: 'string', // Case-insensitive string sort
  },
  {
    Header: 'Price',
    accessor: 'price',
    sortType: 'number', // Numeric sort (strips non-numeric characters)
  },
  {
    Header: 'Created Date',
    accessor: 'createdAt',
    sortType: 'datetime', // Date sorting
    Cell: ({ value }) => new Date(value).toLocaleDateString(),
  },
  {
    Header: 'Product Code',
    accessor: 'code',
    sortType: 'alphanumeric', // Mixed text/number sort
  },
  {
    Header: 'Status',
    accessor: 'status',
    sortType: 'basic', // Simple comparison
  },
];

Multi-Column Sorting

Enable sorting by multiple columns simultaneously with intuitive user interactions.

function MultiSortTable({ columns, data }) {
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
    state: { sortBy },
  } = useTable(
    {
      columns,
      data,
      // Multi-sort configuration
      disableMultiSort: false,
      maxMultiSortColCount: 3, // Limit to 3 columns
      isMultiSortEvent: (e) => e.shiftKey, // Use Shift+click for multi-sort
    },
    useSortBy
  );

  return (
    <div>
      <div>
        <small>
          Hold Shift and click column headers to sort by multiple columns.
          {sortBy.length > 1 && ` Currently sorting by ${sortBy.length} columns.`}
        </small>
      </div>

      <table {...getTableProps()}>
        <thead>
          {headerGroups.map(headerGroup => (
            <tr {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.map(column => (
                <th {...column.getHeaderProps(column.getSortByToggleProps())}>
                  <div>
                    {column.render('Header')}
                    {column.isSorted && (
                      <span>
                        {column.isSortedDesc ? ' ↓' : ' ↑'}
                        {sortBy.length > 1 && (
                          <sup>{column.sortedIndex + 1}</sup>
                        )}
                      </span>
                    )}
                  </div>
                </th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody {...getTableBodyProps()}>
          {rows.map(row => {
            prepareRow(row);
            return (
              <tr {...row.getRowProps()}>
                {row.cells.map(cell => (
                  <td {...cell.getCellProps()}>
                    {cell.render('Cell')}
                  </td>
                ))}
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

Custom Sort Functions

Create custom sorting logic for specialized data types or business requirements.

/**
 * Custom sort function signature
 * @param rowA - First row to compare
 * @param rowB - Second row to compare
 * @param columnId - ID of the column being sorted
 * @param desc - Whether sorting in descending order
 * @returns Comparison result (-1, 0, 1)
 */
interface CustomSortFunction {
  (rowA: Row, rowB: Row, columnId: string, desc: boolean): number;
}

Custom Sort Examples:

// Custom sort for semantic versions
const semverSort = (rowA, rowB, columnId) => {
  const a = rowA.values[columnId];
  const b = rowB.values[columnId];
  
  const aParts = a.split('.').map(Number);
  const bParts = b.split('.').map(Number);
  
  for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
    const aPart = aParts[i] || 0;
    const bPart = bParts[i] || 0;
    
    if (aPart < bPart) return -1;
    if (aPart > bPart) return 1;
  }
  
  return 0;
};

// Custom sort for priority levels
const prioritySort = (rowA, rowB, columnId) => {
  const priorityOrder = { 'Low': 1, 'Medium': 2, 'High': 3, 'Critical': 4 };
  const a = priorityOrder[rowA.values[columnId]] || 0;
  const b = priorityOrder[rowB.values[columnId]] || 0;
  
  return a < b ? -1 : a > b ? 1 : 0;
};

// Custom sort for file sizes
const fileSizeSort = (rowA, rowB, columnId) => {
  const parseSize = (size) => {
    const units = { 'B': 1, 'KB': 1024, 'MB': 1024**2, 'GB': 1024**3 };
    const match = size.match(/^(\d+(?:\.\d+)?)\s*([A-Z]+)$/i);
    if (!match) return 0;
    return parseFloat(match[1]) * (units[match[2].toUpperCase()] || 1);
  };
  
  const a = parseSize(rowA.values[columnId]);
  const b = parseSize(rowB.values[columnId]);
  
  return a < b ? -1 : a > b ? 1 : 0;
};

// Register custom sort types
const table = useTable(
  {
    columns,
    data,
    sortTypes: {
      semver: semverSort,
      priority: prioritySort,
      fileSize: fileSizeSort,
    },
  },
  useSortBy
);

Column Sort Configuration

Configure sorting behavior at the column level.

interface ColumnSortConfig {
  /** Sort type name or custom sort function */
  sortType?: string | SortFunction;
  /** Whether this column can be sorted */
  disableSortBy?: boolean;
  /** Whether to sort descending first (instead of ascending) */
  sortDescFirst?: boolean;
  /** Sort method for this specific column */
  sortMethod?: SortFunction;
}

Column Sort Examples:

const columns = [
  {
    Header: 'Name',
    accessor: 'name',
    sortType: 'string',
    sortDescFirst: false, // Sort A-Z first, then Z-A
  },
  {
    Header: 'Created Date',
    accessor: 'createdAt',
    sortType: 'datetime',
    sortDescFirst: true, // Sort newest first by default
  },
  {
    Header: 'File Size',
    accessor: 'size',
    sortType: 'fileSize', // Custom sort type
  },
  {
    Header: 'Actions',
    id: 'actions',
    disableSortBy: true, // Not sortable
    Cell: ({ row }) => <button>Edit</button>,
  },
  {
    Header: 'Score',
    accessor: 'score',
    // Inline custom sort function
    sortType: (rowA, rowB, columnId) => {
      const a = rowA.values[columnId];
      const b = rowB.values[columnId];
      
      // Handle null/undefined scores
      if (a == null) return b == null ? 0 : -1;
      if (b == null) return 1;
      
      return a < b ? -1 : a > b ? 1 : 0;
    },
  },
];

Manual Sorting

Take control of sorting logic for server-side or custom implementations.

function ManualSortTable({ columns, data }) {
  const [sortBy, setSortBy] = React.useState([
    { id: 'name', desc: false }
  ]);

  // Fetch sorted data based on current sort configuration
  const sortedData = React.useMemo(() => {
    return fetchSortedData(data, sortBy);
  }, [data, sortBy]);

  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
  } = useTable(
    {
      columns,
      data: sortedData,
      manualSortBy: true, // Disable automatic sorting
      // Provide current sort state
      state: {
        sortBy,
      },
      // Handle sort changes
      onSortByChange: setSortBy,
    },
    useSortBy
  );

  return (
    <div>
      <div>
        Server-side sorting: {sortBy.map(sort => 
          `${sort.id} ${sort.desc ? 'desc' : 'asc'}`
        ).join(', ')}
      </div>
      
      <table {...getTableProps()}>
        {/* Table implementation */}
      </table>
    </div>
  );
}

Advanced Sorting Features

Additional sorting utilities and methods for complex scenarios.

interface SortByUtilities {
  /** Default multi-column sorting function */
  defaultOrderByFn: (
    rows: Row[],
    sortBy: SortingRule[],
    sortTypes: SortTypes
  ) => Row[];
}

Custom Sorting Algorithm Example:

// Custom multi-column sort with null handling
const customOrderByFn = (rows, sortBy, sortTypes) => {
  return rows.slice().sort((rowA, rowB) => {
    for (let i = 0; i < sortBy.length; i++) {
      const { id, desc } = sortBy[i];
      const sortType = sortTypes[id] || sortTypes.alphanumeric;
      
      let result = sortType(rowA, rowB, id, desc);
      
      // Handle null/undefined values
      const aValue = rowA.values[id];
      const bValue = rowB.values[id];
      
      if (aValue == null && bValue == null) {
        result = 0;
      } else if (aValue == null) {
        result = desc ? 1 : -1; // Nulls last
      } else if (bValue == null) {
        result = desc ? -1 : 1; // Nulls last
      }
      
      if (result !== 0) {
        return desc ? -result : result;
      }
    }
    return 0;
  });
};

const table = useTable(
  {
    columns,
    data,
    orderByFn: customOrderByFn,
  },
  useSortBy
);

Sort Toggle Props

The getSortByToggleProps method provides attributes for interactive sort controls.

// Basic usage with default click handler
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
  {column.render('Header')}
</th>

// Custom click handler while keeping sort functionality
<th {...column.getHeaderProps(column.getSortByToggleProps({
  onClick: (e) => {
    console.log('Sorting column:', column.id);
    // Default sort handling will still occur
  },
  className: 'sortable-header',
  style: { userSelect: 'none' }
}))}>
  {column.render('Header')}
</th>

// Completely custom sort button
<th {...column.getHeaderProps()}>
  {column.render('Header')}
  {column.canSort && (
    <button
      onClick={() => column.toggleSortBy()}
      className={column.isSorted ? 'sorted' : ''}
    >
      Sort {column.isSorted && (column.isSortedDesc ? '↓' : '↑')}
    </button>
  )}
</th>

Types

interface TableState {
  /** Current sort configuration */
  sortBy: SortingRule[];
}

interface SortingRule {
  /** Column ID */
  id: string;
  /** Sort direction */
  desc: boolean;
}

interface SortTypes {
  [key: string]: SortFunction;
}

interface SortMeta {
  instance: TableInstance;
  column: Column;
}

Install with Tessl CLI

npx tessl i tessl/npm-react-table

docs

built-in-types.md

column-management.md

core-table.md

filtering.md

grouping.md

index.md

layout.md

pagination.md

row-features.md

sorting.md

utilities.md

tile.json