Lattice Grid Buy a licence

demo D52

Custom comparators in React

Sorting by priority words, versions, or a domain rule

value.compare

You are looking at an incident queue of two thousand rows where three columns carry an order the alphabet gets wrong. Sort Priority and it ranks by real severity rather than A to Z, sort Version and 2.10.0 lands after 2.9.0 not before it, and sort Owner and the unassigned rows stay at the bottom whichever way you sort.

Building…
Loading a live grid…

This is the React version. The grid mounts through the createLatticeGrid adapter, which takes its configuration as ordinary props and hands back the live grid through a ref. The grid below is the same one every other tab runs.

The configuration

<link rel="stylesheet" href="https://cdn.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.min.css">

<div id="grid"></div>

<script type="module">
  import React from 'https://esm.sh/react@18';
  import { createRoot } from 'https://esm.sh/react-dom@18/client';
  import { createGrid } from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.esm.min.js';
  import createLatticeGrid from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/modules/react.esm.min.js';

  const LatticeGrid = createLatticeGrid({ React, createGrid });

  // Order by a known ladder rather than by the words themselves: alphabetical
  // would put low above medium.
  const PRIORITIES = ['critical', 'high', 'medium', 'low', 'none'];
  const byPriority = (a, b) => PRIORITIES.indexOf(String(a)) - PRIORITIES.indexOf(String(b));

  // Compare dotted release numbers as numbers, with pre-releases before the
  // release they lead to, so 1.10.0 sorts after 1.9.3 and 2.0.0 after 2.0.0-rc.1.
  const bySemver = (a, b) => {
    const parse = (value) => {
      const [core, pre = null] = String(value == null ? '' : value).split('-');
      return { parts: core.split('.').map((n) => Number(n) || 0), pre };
    };
    const left = parse(a), right = parse(b);
    for (let i = 0; i < 3; i++) {
      const diff = (left.parts[i] || 0) - (right.parts[i] || 0);
      if (diff !== 0) return diff;
    }
    if (left.pre === right.pre) return 0;
    if (left.pre === null) return 1;
    if (right.pre === null) return -1;
    return left.pre < right.pre ? -1 : 1;
  };

  // Keep the unassigned rows at the bottom in both directions. The grid inverts a
  // comparator's result for a descending sort, so a row that must stay put is
  // pushed the other way when the fifth argument says the sort is descending.
  const ownersLast = (a, b, rowA, rowB, descending) => {
    const blankA = a == null || a === '';
    const blankB = b == null || b === '';
    if (blankA && blankB) return 0;
    if (blankA) return descending ? -1 : 1;
    if (blankB) return descending ? 1 : -1;
    return String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0;
  };

  const columns = [
    { field: 'id', title: 'Incident', layout: { width: 130, pin: 'start' } },
    { field: 'priority', title: 'Priority', layout: { width: 120 }, value: { compare: byPriority },
      cell: { decoration: 'pill', variant: { map: { critical: 'danger', high: 'warning', medium: 'info', low: 'neutral', none: 'neutral' } } } },
    { field: 'version', title: 'Version', layout: { width: 150 }, value: { compare: bySemver } },
    { field: 'owner', title: 'Owner', layout: { width: 150 }, value: { compare: ownersLast } },
    { field: 'service', title: 'Service', filter: { type: 'set' } },
    { field: 'summary', title: 'Summary', layout: { flex: 1, min: 240 } },
    { field: 'ageHours', title: 'Age', type: 'number', format: { suffix: ' h' }, layout: { width: 110 } },
  ];

  const rows = [/* incident records */];

  function App() {
    return (
      <LatticeGrid
        rowKey="id"
        columns={columns}
        rows={rows}
        style={{ height: '540px' }}
      />
    );
  }

  createRoot(document.getElementById('grid')).render(<App />);
</script>

Sorting by a custom comparator instead of default ordering

Default sorting compares values with the ordinary less-than rules for numbers and strings, which breaks down as soon as a column carries meaning a plain comparison cannot see: priority words that should read Low, Medium, High rather than alphabetically, semantic version strings where “2.10.0” needs to land after “2.9.0” rather than before it, or a domain-specific rank that only the application knows. A developer reaches for a custom comparator whenever the natural order of a column is not its lexical or numeric order. Lattice Grid exposes this through value.compare, a column option taking a function that receives two cell values and returns the same negative, zero, or positive result a standard JavaScript Array.prototype.sort comparator would, so existing comparator functions for version strings or ranked enums drop in without adaptation. The comparator runs only during a sort operation, not on every render, so attaching one to a column of custom-ranked categories costs nothing while the grid is idle and adds no overhead to scrolling or filtering. Sort state set through a custom comparator is a plain { col, dir } pair like any other sort, so it serialises the same way for a saved view or a shareable link, and the header retains its usual ascending and descending arrow regardless of how the ordering underneath it is computed.

How do I sort a column by custom logic instead of alphabetical or numeric order?

Set value.compare on the column definition to a function taking two cell values and returning negative, zero, or positive, the same contract as a standard array sort comparator. Lattice Grid calls it during the sort pass in place of the default comparison, so a column of priority labels or version strings sorts by the rule the function encodes rather than by character order.