Replies: 2 comments
|
You can use creator function with type parameter: import { type ColumnDef } from "@tanstack/react-table";
interface MyTableData {
name: string;
resourceId: string | undefined;
}
function createResourceIdColumn<
TData extends { resourceId: string | undefined },
>(): ColumnDef<TData> {
const columnResourceId: ColumnDef<TData> = {
accessorKey: "resourceId",
cell: (info) => <div>{info.row.original.resourceId}</div>,
header: "RESOURCE ID",
};
return columnResourceId;
}
const columns: Array<ColumnDef<MyTableData>> = [
{
accessorKey: "name",
cell: (info) => <div>{info.row.original.name}</div>,
},
// No error here:
createResourceIdColumn(),
]; |
|
The key to making reusable column definitions in TanStack Table without losing TypeScript inference is to use a generic factory function constrained by an interface (structural typing). Here is the production pattern that allows any table model containing 1. Generic Column Factory with Structural ConstraintDefine an interface describing only the property that this column needs, and constrain the generic import type { ColumnDef, Row } from '@tanstack/react-table';
// Minimal interface required by this reusable column
export interface HasResourceId {
resourceId?: string | null;
}
export function createResourceIdColumn<TData extends HasResourceId>(
overrides?: Partial<ColumnDef<TData, string | null | undefined>>
): ColumnDef<TData, string | null | undefined> {
return {
id: 'resourceId',
accessorKey: 'resourceId',
header: 'Resource ID',
cell: ({ getValue }) => {
const val = getValue();
return val ? (
<code className="font-mono text-xs">{val}</code>
) : (
<span className="text-gray-400">—</span>
);
},
enableSorting: true,
...overrides,
};
}2. Consuming Across Different TablesEach table defines its own row type, and TypeScript guarantees compile-time verification: // Table A: Users
interface UserRow {
id: string;
name: string;
resourceId?: string; // Satisfies HasResourceId
}
const userColumns: ColumnDef<UserRow>[] = [
{ accessorKey: 'name', header: 'User' },
// Fully type-checked:
createResourceIdColumn<UserRow>(),
];
// Table B: Orders
interface OrderRow {
orderNumber: string;
total: number;
resourceId: string; // Also satisfies HasResourceId
}
const orderColumns: ColumnDef<OrderRow>[] = [
{ accessorKey: 'orderNumber', header: 'Order #' },
// Can pass column overrides if this table needs a custom header or width:
createResourceIdColumn<OrderRow>({
header: 'Origin Resource',
size: 150,
}),
];
// ❌ TypeScript Error: Missing 'resourceId'
interface ProductRow {
title: string;
}
// Type 'ProductRow' does not satisfy the constraint 'HasResourceId'.
createResourceIdColumn<ProductRow>(); 3. Alternative: Path-Agnostic Accessor FactoryIf different tables store the resource ID under different property names or nested objects (e.g. export function createSharedIdColumn<TData>(
accessor: keyof TData | ((row: TData) => string | undefined),
headerTitle: string = 'Resource ID'
): ColumnDef<TData, any> {
return {
header: headerTitle,
accessorFn: typeof accessor === 'function' ? accessor : (row) => row[accessor],
cell: ({ getValue }) => {
const val = getValue();
return val ? <code className="font-mono">{val}</code> : <span>—</span>;
},
};
}Why this works best:
|
Uh oh!
There was an error while loading. Please reload this page.
My app has a bunch of tables that have similar columns, and I'm trying to ensure those columns look / behave identically across all tables. These definitions are bit involved, with custom headers and value displays, and it's very easy for me to make a change to that column definition in one table but forget to update other tables to match. So I want to extract that column definition to be reusable by multiple tables.
I'm stuck on making that definition type-safe. For instance, if I have a column called
resourceIdthat's typed asstring | undefined, then I could have something like:And then insert it into my table definition as such: (
MyTableDataincludes aresourceIdkey):This works just fine, but Typescript complains with an error similar to:
So is there a proper way of typing sharable column definitions, or is this something that's not supported and my code above may break in some situations?
All reactions