Skip to content

sortUndefined and Excel like sorting #6061

Open

Description

@philipp985

TanStack Table version

8.21.3

Framework/Library version

React 18.2

Describe the bug and the steps to reproduce it

Hello and thanks for that fantastic library!
I have a question or a bug when creating an Excel like sorting. My problem is to get the null/undefined results always to the bottom of the table. I would have assumed that sortUndefined would do the trick but it did not. Therefore I tried to create a custom sorting for that. But I can not manage to geht the sorting correct.
Correct (Excel Like) would be, if my table could do that

Excel Behavior Supported in excelSortingFn?
Numbers before text ✅ Yes
Booleans in correct order ✅ Yes
Case-insensitive strings ✅ Yes
Empty/null values last ❌ Problem
No coercion of string "123" ✅ Yes

I added a simple code sandbox link below to try it out. Any help would be greatly appreciated.

Your Minimal, Reproducible Example - (Sandbox Highly Recommended)

https://codesandbox.io/p/sandbox/boring-ellis-sw7h86

Screenshots or Videos (Optional)

No response

Do you intend to try to help solve this bug with your own PR?

No, because I do not know how

Terms & Code of Conduct

  • I agree to follow this project's Code of Conduct
  • I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.

Activity

  1. baskvava commented on Aug 5, 2025

    @baskvava

    Hello @philipp985

    After further investigation and experimentation, I've found a solution that achieves the desired "Excel-like" sorting while respecting all the constraints. I'm posting it here and hope it can solve your problem.

    1. Add accessorFn and cell under column definition
    accessorFn: (originalRow) => {
      const value = originalRow.name;
      if (value === null || value === "" || value === undefined) {
        return undefined;
      }
      return value;
    },
    cell: (info) => {
      const originalValue = info.row.original.name;
      return String(originalValue);
    },
    1. Replace cell getValue() as cell getContent() and render into flexRender() into the table

    This is final version for App.tsx

    // App.tsx
    
    import React from "react";
    import {
      useReactTable,
      getCoreRowModel,
      getSortedRowModel,
      ColumnDef,
      flexRender,
    } from "@tanstack/react-table";
    import { excelSortingFn } from "./excelSortingFn";
    
    type RowData = { name: string | null | undefined };
    
    const data: RowData[] = [
      { name: "Apple" },
      { name: "" },
      { name: null },
      { name: "banana" },
      { name: "Zebra" },
      { name: undefined },
    ];
    
    const columns: ColumnDef<RowData>[] = [
      {
        header: "Name",
        accessorKey: "name",
        // add accessorFn
        accessorFn: (originalRow) => {
          const value = originalRow.name;
          if (value === null || value === "" || value === undefined) {
            return undefined;
          }
          return value;
        },
        // add cell
        cell: (info) => {
          const originalValue = info.row.original.name;
          return String(originalValue);
        },
        sortingFn: excelSortingFn,
        sortUndefined: "last",
      },
    ];
    
    export default function App() {
      const [sorting, setSorting] = React.useState([]);
      const table = useReactTable({
        data,
        columns,
        state: { sorting },
        onSortingChange: setSorting,
        getCoreRowModel: getCoreRowModel(),
        getSortedRowModel: getSortedRowModel(),
      });
    
      return (
        <table style={{ borderCollapse: "collapse", width: "100%" }}>
          <thead>
            {table.getHeaderGroups().map((hg) => (
              <tr key={hg.id}>
                {hg.headers.map((h) => (
                  <th
                    key={h.id}
                    onClick={h.column.getToggleSortingHandler()}
                    style={{ cursor: "pointer", borderBottom: "1px solid black" }}
                  >
                    {h.column.id}{" "}
                    {{ asc: "🔼", desc: "🔽" }[h.column.getIsSorted() as string] ??
                      null}
                  </th>
                ))}
              </tr>
            ))}
          </thead>
          <tbody>
            {table.getRowModel().rows.map((r) => (
              <tr key={r.id}>
                {r.getVisibleCells().map((c) => (
                  <td
                    key={c.id}
                    style={{ padding: "4px", borderBottom: "1px solid lightgray" }}
                  >
                     {/* replace here*/}
                    {flexRender(c.column.columnDef.cell, c.getContext())}
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      );
    }
  2. ippeiukai commented on Jun 22, 2026

    @ippeiukai

    I'm trying to migrate existing tables to v8. In v7, sorting functions received desc flag that can be used to keep some rows always at the bottom.

    For ease of migration, it would still be nice to have more generalized option than sortUndefined, like this:

      // `sortUndefined: 'last'` equivalent
      sortFixed: { match: undefined, position: 'last' }
    
      // Pin null to always appear last
      sortFixed: { match: null, position: 'last' }
    
      // Pin empty values to always appear last
      sortFixed: { match: (v) => v == null || v === '', position: 'last' }
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions