# Example: Invoice due dates

Switch locales and sort canonical invoice dates while comparing their formatted display with unchanged stored values.

Source: https://pretable.ai/examples/date-formatting.md

```tsx InvoiceDatesGrid.tsx
"use client";

import { useId, useState, type ComponentProps } from "react";
import { PretableButton, PretableSurface } from "@pretable/react";

import { columns } from "./columns";
import { invoices, type Invoice } from "./data";

export function InvoiceDatesGrid() {
  const localeId = useId();
  const [locale, setLocale] = useState("en-US");
  const [query, setQuery] = useState<
    NonNullable<ComponentProps<typeof PretableSurface<Invoice>>["query"]>
  >({
    filters: [],
    sort: [{ columnId: "due", direction: "asc" }],
    rowGroups: [],
  });

  return (
    <div style={{ fontSize: 13 }}>
      <div
        style={{
          display: "flex",
          flexWrap: "wrap",
          alignItems: "center",
          gap: 12,
          marginBottom: 12,
        }}
      >
        <label htmlFor={localeId}>Date locale</label>
        <select
          id={localeId}
          value={locale}
          onChange={(event) => setLocale(event.target.value)}
        >
          <option value="en-US">English (US)</option>
          <option value="en-GB">English (UK)</option>
          <option value="de-DE">German</option>
        </select>
        <PretableButton
          onClick={() =>
            setQuery((current) => ({
              ...current,
              sort: [{ columnId: "due", direction: "asc" }],
            }))
          }
        >
          Earliest first
        </PretableButton>
        <PretableButton
          onClick={() =>
            setQuery((current) => ({
              ...current,
              sort: [{ columnId: "due", direction: "desc" }],
            }))
          }
        >
          Latest first
        </PretableButton>
      </div>
      <PretableSurface
        ariaLabel="Invoice due dates"
        columns={columns}
        rows={invoices}
        locale={locale}
        query={query}
        onQueryChange={setQuery}
        viewportHeight={280}
      />
      <p style={{ margin: "12px 0 0" }}>
        Locale changes the display, not the stored date. INV-104 has no due date
        and stays last in both date sort directions.
      </p>
    </div>
  );
}
```

```ts columns.ts
import { createColumnHelper } from "@pretable/core";

import type { Invoice } from "./data";

const column = createColumnHelper<Invoice>();

export const columns = [
  column.accessor("id", { type: "text", header: "Invoice", widthPx: 100 }),
  column.accessor("due", {
    header: "Due date",
    type: "date",
    dateFormat: { dateStyle: "medium" },
    widthPx: 150,
  }),
  column.accessor("rawDue", (row) => row.due, {
    type: "text",
    header: "Stored value",
    format: ({ value }) => (value === null ? "null" : String(value)),
    widthPx: 130,
  }),
] as const;
```

```ts data.ts
export interface Invoice {
  id: string;
  due: string | null;
}

// Store calendar dates, not JavaScript Date objects or localized strings.
export const invoices: Invoice[] = [
  { id: "INV-101", due: "2026-09-18" },
  { id: "INV-102", due: "2026-01-15" },
  { id: "INV-103", due: "2026-12-02" },
  { id: "INV-104", due: null },
];
```
