Skip to content

TanStack Table

TanStack Table column definitions require an accessor function and an id string when using a function accessor. These two values always refer to the same field — they must stay in sync manually. data-path keeps them in sync automatically: .fn provides the accessor function and .$ provides the string id.

import { createColumnHelper } from "@tanstack/react-table";
import { path } from "data-path";
type User = { id: string; contact: { email: string }; name: string };
const columnHelper = createColumnHelper<User>();
const emailPath = path((u: User) => u.contact.email);
const namePath = path((u: User) => u.name);
const columns = [
columnHelper.accessor(namePath.fn, {
id: namePath.$,
header: "Name",
cell: (info) => info.getValue(),
}),
columnHelper.accessor(emailPath.fn, {
id: emailPath.$,
header: "Email",
cell: (info) => info.getValue(),
}),
];

emailPath.fn is (u: User) => string | undefined — TanStack Table expects (row: T) => V. The types align directly.

Deep paths work the same way — the column accessor and id stay in sync regardless of nesting depth:

type Order = {
id: string;
customer: { profile: { firstName: string; lastName: string } };
total: number;
};
const firstNamePath = path((o: Order) => o.customer.profile.firstName);
columnHelper.accessor(firstNamePath.fn, {
id: firstNamePath.$, // "customer.profile.firstName"
header: "First name",
})

The path string is also useful when configuring column sort fields or filter keys that reference the same property:

const columns = [
columnHelper.accessor(emailPath.fn, {
id: emailPath.$,
header: "Email",
enableSorting: true,
sortingFn: "alphanumeric",
}),
];
// In global filter, compare against path string
const rowMatchesFilter = (row: User, filter: string) =>
emailPath.get(row)?.includes(filter) ?? false;

Build a column registry where path definitions drive both the accessor and the display name. Use Path<T, V> as the prop type, and specialize the helper to your row type (the ColumnHelper returned by createColumnHelper<User>() is already tied to User):

import type { Path } from "data-path";
function col<V>(p: Path<User, V>, header: string) {
return columnHelper.accessor(p.fn, { id: p.$, header });
}
const columns = [
col(namePath, "Name"),
col(emailPath, "Email"),
];

If you want a row-type-agnostic helper, pass the columnHelper in and make the factory itself generic:

import type { ColumnHelper } from "@tanstack/react-table";
import type { Path } from "data-path";
function makeCol<T>(helper: ColumnHelper<T>) {
return <V>(p: Path<T, V>, header: string) =>
helper.accessor(p.fn, { id: p.$, header });
}
const col = makeCol(columnHelper);
const columns = [col(namePath, "Name"), col(emailPath, "Email")];