Creating React Sortable Table

Build performant, scalable data tables with React. From custom hooks to enterprise-grade libraries like TanStack Table.

Why Sortable Tables Matter in Modern Applications

Sortable tables are one of the most common UI components in modern web applications. Whether you're building a dashboard to display analytics data, an inventory management system, or a customer relationship manager, the ability to sort data by columns is essential for user experience. React's component-based architecture makes building sortable tables intuitive, with multiple approaches depending on your project requirements.

In this guide, we'll explore how to create React sortable tables that perform well, scale with your application, and integrate seamlessly with modern frameworks like Next.js. We'll cover both building from scratch and using established libraries like TanStack Table, so you can make informed decisions about which approach best fits your needs.

Our approach to web development emphasizes clean architecture, performance optimization, and maintainable code--principles that apply directly to how you build data-intensive components like sortable tables.

What You'll Learn

Custom Hook Implementation

Build sortable tables from scratch using React hooks for complete control

TanStack Table Integration

Use the industry-standard library for enterprise-grade table functionality

Performance Optimization

Master memoization and virtualization for large datasets

Next.js Best Practices

Optimize tables for SSR and Core Web Vitals

Building a Sortable Table from Scratch

The most straightforward approach involves managing sort state using useState and implementing a sort function that operates on your data array. This gives you complete control over sorting behavior--ideal for smaller datasets or when avoiding external dependencies.

For projects using TypeScript, defining proper TypeScript interfaces for props and state ensures type safety throughout your component. This approach also pairs well with our custom software development expertise for building tailored solutions that match your exact requirements.

Basic SortableTable Component
1import React, { useState, useMemo } from 'react';2 3type SortDirection = 'asc' | 'desc';4 5interface SortState {6 column: string | null;7 direction: SortDirection;8}9 10export function SortableTable<T>({ data, columns }: SortableTableProps<T>) {11 const [sortState, setSortState] = useState<SortState>({12 column: null,13 direction: 'asc'14 });15 16 const sortedData = useMemo(() => {17 if (!sortState.column) return data;18 19 return [...data].sort((a, b) => {20 const aValue = a[sortState.column!];21 const bValue = b[sortState.column!];22 23 if (aValue < bValue) return sortState.direction === 'asc' ? -1 : 1;24 if (aValue > bValue) return sortState.direction === 'asc' ? 1 : -1;25 return 0;26 });27 }, [data, sortState.column, sortState.direction]);28 29 const handleSort = (column: string) => {30 setSortState(prev => ({31 column,32 direction: prev.column === column && prev.direction === 'asc' ? 'desc' : 'asc'33 }));34 };35 36 return (37 <table>38 <thead>39 <tr>40 {columns.map(col => (41 <th key={col.key} onClick={() => handleSort(col.key)}>42 {col.label}43 {sortState.column === col.key && (44 <span>{sortState.direction === 'asc' ? ' ▲' : ' ▼'}</span>45 )}46 </th>47 ))}48 </tr>49 </thead>50 <tbody>51 {sortedData.map((row, i) => (52 <tr key={i}>53 {columns.map(col => <td key={col.key}>{row[col.key]}</td>)}54 </tr>55 ))}56 </tbody>57 </table>58 );59}

Key Implementation Details

  • Sort State Management: Track which column is sorted and the direction (ascending/descending)
  • Stable Sorting: Use [...data].sort() to avoid mutating the original array
  • Type Safety: Define TypeScript interfaces for props and state
  • Memoization: Use useMemo to prevent unnecessary re-sorts

Handling Edge Cases

Real-world data often contains null values, inconsistent formatting, or special characters. A robust implementation handles:

  • Null and undefined values gracefully
  • Case-insensitive string sorting
  • Numeric parsing for formatted numbers
  • Stable sorting to maintain original order for equal values

These same principles apply to other interactive React components. For more on building React hooks effectively, see our guide to React hooks for common problems.

Using TanStack Table for Production Applications

TanStack Table (formerly React Table) has emerged as the de facto standard for building data tables in React. This headless UI library provides building blocks for powerful table components without imposing styling decisions. The library's architecture aligns well with our enterprise software development approach, providing flexibility while maintaining code quality.

For production applications requiring robust table functionality--filtering, pagination, sorting, and large datasets--TanStack Table offers a well-tested foundation that accelerates development without sacrificing customization.

TanStack Table with Sorting
1import {2 useReactTable,3 getCoreRowModel,4 getSortedRowModel,5 flexRender,6 ColumnDef,7 SortingState8} from '@tanstack/react-table';9import { useState } from 'react';10 11interface User {12 id: number;13 name: string;14 email: string;15 role: string;16 status: 'active' | 'inactive';17}18 19export function DataTable({ data }: { data: User[] }) {20 const [sorting, setSorting] = useState<SortingState>([]);21 22 const columns = useMemo<ColumnDef<User>[]>(23 () => [24 { accessorKey: 'name', header: 'Name' },25 { accessorKey: 'email', header: 'Email' },26 { accessorKey: 'role', header: 'Role' },27 {28 accessorKey: 'status',29 header: 'Status',30 cell: ({ row }) => (31 <span className={`badge ${row.original.status}`}>32 {row.original.status}33 </span>34 )35 }36 ],37 []38 );39 40 const table = useReactTable({41 data,42 columns,43 state: { sorting },44 onSortingChange: setSorting,45 getCoreRowModel: getCoreRowModel(),46 getSortedRowModel: getSortedRowModel(),47 });48 49 return (50 <table className="data-table">51 <thead>52 {table.getHeaderGroups().map(headerGroup => (53 <tr key={headerGroup.id}>54 {headerGroup.headers.map(header => (55 <th56 key={header.id}57 onClick={header.column.getToggleSortingHandler()}58 >59 {flexRender(60 header.column.columnDef.header,61 header.getContext()62 )}63 {{64 asc: ' ▲',65 desc: ' ▼',66 }[header.column.getIsSorted() as string] ?? null}67 </th>68 ))}69 </tr>70 ))}71 </thead>72 <tbody>73 {table.getRowModel().rows.map(row => (74 <tr key={row.id}>75 {row.getVisibleCells().map(cell => (76 <td key={cell.id}>77 {flexRender(cell.column.columnDef.cell, cell.getContext())}78 </td>79 ))}80 </tr>81 ))}82 </tbody>83 </table>84 );85}

Why TanStack Table for Enterprise Apps?

  • Headless Architecture: Complete control over styling and markup
  • TypeScript First: Excellent type safety out of the box
  • Tree-Shakable: Include only the features you need
  • Multi-Column Sorting: Sort by multiple columns with priority order
  • Custom Comparators: Handle complex data types with custom sorting logic
  • Active Maintenance: Well-maintained with regular updates and strong community support

For enterprise dashboards and data-intensive applications, combining TanStack Table with Node.js backend services creates a powerful full-stack solution for managing and displaying large datasets efficiently.

Performance Optimization for Large Datasets

When working with large datasets--thousands or tens of thousands of rows--React's default rendering can become a performance bottleneck. Our performance-first development methodology emphasizes proactive optimization to ensure smooth user experiences.

The main concerns are unnecessary re-renders of unchanged rows, expensive comparison operations during sorting, and memory consumption for large data arrays. Addressing these requires a combination of React optimization techniques and architectural decisions aligned with Core Web Vitals best practices.

Optimization Techniques

React.memo for Rows

Prevent re-rendering of unchanged rows by memoizing row components

useMemo for Sorting

Cache sorted results to avoid recalculating on every render

Virtualization

Only render visible rows for datasets with 10K+ rows

Windowing

Combine with react-window for smooth scrolling on large tables

Memoized Table Row with Virtualization
1import { memo } from 'react';2import { useVirtualizer } from '@tanstack/react-virtual';3 4// Memoized row component - only re-renders when data changes5const TableRow = memo(({ row, style }: TableRowProps) => (6 <tr style={style}>7 {row.getVisibleCells().map(cell => (8 <td key={cell.id}>9 {flexRender(cell.column.columnDef.cell, cell.getContext())}10 </td>11 ))}12 </tr>13));14 15TableRow.displayName = 'TableRow';16 17// Virtualized table implementation18export function VirtualizedTable({ data, columns }: Props) {19 const parentRef = useRef<HTMLDivElement>(null);20 21 const rowVirtualizer = useVirtualizer({22 count: data.length,23 getScrollElement: () => parentRef.current,24 estimateSize: () => 48, // Estimated row height in pixels25 overscan: 10, // Number of rows to render outside viewport26 });27 28 return (29 <div ref={parentRef} className="table-container" style={{ height: '600px', overflow: 'auto' }}>30 <table>31 <thead>32 <tr>{columns.map(col => <th key={col.key}>{col.label}</th>)}</tr>33 </thead>34 <tbody35 style={{36 height: `${rowVirtualizer.getTotalSize()}px`,37 position: 'relative'38 }}39 >40 {rowVirtualizer.getVirtualItems().map(virtualRow => {41 const row = data[virtualRow.index];42 return (43 <TableRow44 key={row.id}45 row={row}46 style={{47 position: 'absolute',48 top: 0,49 left: 0,50 width: '100%',51 height: `${virtualRow.size}px`,52 transform: `translateY(${virtualRow.start}px)`53 }}54 />55 );56 })}57 </tbody>58 </table>59 </div>60 );61}

Next.js Integration Best Practices

Building sortable tables in Next.js requires careful consideration of server-side rendering, client-side interactivity, and Core Web Vitals optimization. Our team specializes in Next.js development services that balance performance with functionality.

Sortable tables are inherently interactive--users expect immediate feedback when clicking column headers. However, for SEO and initial page load performance, you may want to render the initial table state on the server. This hybrid approach provides the best of both worlds: search engine visibility and snappy interactions.

Core Web Vitals Optimization

Sortable tables can impact key metrics:

  • LCP (Largest Contentful Paint): Lazy-load table data and use skeleton states
  • CLS (Cumulative Layout Shift): Reserve space for table content and avoid dynamic height changes
  • INP (Interaction to Next Paint): Debounce sort operations and show immediate feedback

Recommended Approach for Next.js

  1. Fetch initial data on the server (great for SEO)
  2. Render table in initial sort order
  3. Implement sorting with client-side state
  4. Use Suspense for loading states
  5. Optimize images and assets within cells

For applications requiring advanced data handling, consider combining Next.js with Supabase for real-time data updates and efficient data management.

Frequently Asked Questions

Ready to Build High-Performance React Applications?

Our team specializes in building custom web applications with modern React patterns. From sortable tables to complex dashboards, we deliver performant solutions.

Sources

  1. LogRocket: Creating a React Sortable Table - Comprehensive tutorial on building sortable tables from scratch
  2. Contentful: A Complete Guide to TanStack Table - Enterprise-grade table implementation patterns
  3. Strapi: Build Tables in React - Data Grid Performance Guide - Performance optimization for large datasets