Type-safe grid configuration with TypeScript.
The ActiveGrid is built with TypeScript and provides full type safety. Use generics to ensure type-safe column definitions, callbacks, and data access throughout your grid.
type User = {
id: string;
name: string;
email: string;
};
const columns: GridColumnDef<User>[] = [
{ accessorKey: 'name', header: 'Name' }, // ✓ Type-safe
{ accessorKey: 'foo', header: 'Foo' }, // ✗ Error: 'foo' not in User
];
<ActiveGrid<User>
columns={columns}
data={users}
{...}
/>Pass your data type as a generic parameter:
type Product = {
id: string;
name: string;
price: number;
inStock: boolean;
};
<ActiveGrid<Product>
columns={columns}
data={products}
onRowClick={(row) => {
// row is typed as Product
console.log(row.name);
}}
/>type User = {
id: string;
name: string;
email: string;
age: number;
};
const columns: GridColumnDef<User>[] = [
{
accessorKey: 'name', // ✓ Type-safe: 'name' is in User
header: 'Name',
},
{
accessorKey: 'email', // ✓ Type-safe
header: 'Email',
},
{
accessorKey: 'invalid', // ✗ Error: 'invalid' not in User
header: 'Invalid',
},
];{
accessorFn: (row: User) => `${row.name} (${row.age})`,
id: 'nameWithAge',
header: 'Name & Age',
}{
accessorKey: 'age',
header: 'Age',
cell: ({ getValue }) => {
const age = getValue() as number; // Typed as number
return <span>{age} years old</span>;
},
}<ActiveGrid<User>
onRowClick={(row: User) => {
console.log(row.id); // ✓ Type-safe
console.log(row.name); // ✓ Type-safe
console.log(row.foo); // ✗ Error: 'foo' not in User
}}
{...}
/>import { EditingCommitPayload } from '@workspace/active-grid';
<ActiveGrid<User>
onDataCommit={async (payload: EditingCommitPayload<User>) => {
// payload.newData is User
// payload.changes is Partial<User>
await api.updateUser(payload.rowId, payload.changes);
}}
{...}
/><ActiveGrid<User>
onCellActivate={(row: User, columnId: string) => {
console.log(row.name, columnId);
}}
{...}
/>import { RowSelectionState } from '@tanstack/react-table';
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
<ActiveGrid<User>
state={{ rowSelection }}
onRowSelectionChange={setRowSelection}
{...}
/>import { SortingState } from '@tanstack/react-table';
const [sorting, setSorting] = useState<SortingState>([
{ id: 'name', desc: false },
]);
<ActiveGrid<User>
state={{ sorting }}
onSortingChange={setSorting}
{...}
/>import { ColumnFiltersState } from '@tanstack/react-table';
const [filters, setFilters] = useState<ColumnFiltersState>([
{ id: 'status', value: 'active' },
]);
<ActiveGrid<User>
state={{ columnFilters: filters }}
onColumnFiltersChange={setFilters}
{...}
/>{
id: 'actions',
header: 'Actions',
cell: ({ row }) => {
const user = row.original; // Typed as User
return (
<Button onClick={() => handleEdit(user.id)}>
Edit {user.name}
</Button>
);
},
}import { useActiveGrid } from '@workspace/active-grid';
function TableActions() {
const table = useActiveGrid<User>();
const selectedRows = table.getSelectedRowModel().rows;
const selectedData: User[] = selectedRows.map(row => row.original);
return <div>{selectedData.length} selected</div>;
}Extend the ColumnMeta interface to add custom properties:
declare module '@tanstack/react-table' {
interface ColumnMeta<TData, TValue> {
customProp?: string;
specialFormat?: (value: TValue) => string;
}
}
// Now you can use custom props
const columns: GridColumnDef<User>[] = [
{
accessorKey: 'name',
header: 'Name',
meta: {
customProp: 'value',
specialFormat: (name) => name.toUpperCase(),
},
},
];import { getSelectionColumn } from '@workspace/active-grid';
const columns: GridColumnDef<User>[] = [
getSelectionColumn<User>(),
...dataColumns,
];import { getRowNumberColumn } from '@workspace/active-grid';
const columns: GridColumnDef<User>[] = [
getRowNumberColumn<User>({
header: '#',
size: 50,
}),
...dataColumns,
];import { ServerFetchParams, PagedResponse } from '@workspace/active-grid';
const fetchUsers = async (
params: ServerFetchParams
): Promise<PagedResponse<User>> => {
const response = await api.getUsers(params);
return {
data: response.users,
totalCount: response.total,
};
};
<ActiveGrid<User>
mode="server"
fetchFn={fetchUsers}
{...}
/>any typetype UserCellProps = {
user: User;
};
const UserCell: React.FC<UserCellProps> = ({ user }) => {
return (
<div>
<div>{user.name}</div>
<div className="text-sm text-muted-foreground">{user.email}</div>
</div>
);
};
// Use in column
{
accessorKey: 'name',
header: 'User',
cell: ({ row }) => <UserCell user={row.original} />,
}import { Column } from '@tanstack/react-table';
type CustomHeaderProps = {
column: Column<User>;
title: string;
};
const CustomHeader: React.FC<CustomHeaderProps> = ({ column, title }) => {
return (
<div onClick={() => column.toggleSorting()}>
{title}
{column.getIsSorted() && '↕'}
</div>
);
};
// Use in column
{
accessorKey: 'name',
header: ({ column }) => <CustomHeader column={column} title="Name" />,
}import { useState, useMemo } from 'react';
import {
ActiveGrid,
GridColumnDef,
getSelectionColumn,
getRowNumberColumn,
} from '@workspace/active-grid';
import {
RowSelectionState,
SortingState,
ColumnFiltersState,
} from '@tanstack/react-table';
type User = {
id: string;
name: string;
email: string;
role: 'admin' | 'user' | 'guest';
isActive: boolean;
createdAt: Date;
};
function UsersTable() {
const [users, setUsers] = useState<User[]>([]);
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [sorting, setSorting] = useState<SortingState>([]);
const [filters, setFilters] = useState<ColumnFiltersState>([]);
const columns = useMemo<GridColumnDef<User>[]>(() => [
getSelectionColumn<User>(),
getRowNumberColumn<User>({ header: '#' }),
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => {
const user = row.original; // Typed as User
return (
<div>
<div>{user.name}</div>
<div className="text-sm">{user.email}</div>
</div>
);
},
},
{
accessorKey: 'role',
header: 'Role',
meta: {
filterType: 'select',
filterOptions: [
{ label: 'Admin', value: 'admin' },
{ label: 'User', value: 'user' },
{ label: 'Guest', value: 'guest' },
],
},
},
{
accessorKey: 'isActive',
header: 'Status',
cell: ({ getValue }) => {
const isActive = getValue() as boolean;
return isActive ? 'Active' : 'Inactive';
},
},
{
id: 'actions',
header: 'Actions',
cell: ({ row }) => (
<Button onClick={() => handleEdit(row.original)}>
Edit
</Button>
),
},
], []);
const handleEdit = (user: User) => {
console.log('Editing:', user.name);
};
const handleRowClick = (user: User) => {
console.log('Clicked:', user.id);
};
const handleDataCommit = async (
payload: EditingCommitPayload<User>
) => {
await api.updateUser(payload.rowId, payload.changes);
};
return (
<ActiveGrid<User>
columns={columns}
data={users}
state={{
rowSelection,
sorting,
columnFilters: filters,
}}
onRowSelectionChange={setRowSelection}
onSortingChange={setSorting}
onColumnFiltersChange={setFilters}
onRowClick={handleRowClick}
onDataCommit={handleDataCommit}
getRowId={(row) => row.id}
/>
);
}// ✗ Error
{
accessorKey: 'nonExistent', // Property 'nonExistent' does not exist on type 'User'
header: 'Invalid',
}
// ✓ Fix
{
accessorKey: 'name', // Valid property
header: 'Name',
}// ✗ Error
cell: ({ getValue }) => {
const value = getValue(); // Type is unknown
return value.toUpperCase(); // Error
}
// ✓ Fix
cell: ({ getValue }) => {
const value = getValue() as string; // Cast to correct type
return value.toUpperCase();
}// ✗ Error
const table = useActiveGrid(); // Returns Table<unknown>
// ✓ Fix
const table = useActiveGrid<User>(); // Returns Table<User>