Using generics for type-safe data handling.
TypeScript generics enable full type safety throughout your ActiveGrid implementation. Pass your data type as a generic parameter to get autocomplete, type checking, and IntelliSense support.
type User = {
id: string;
name: string;
email: string;
};
// Type-safe ActiveGrid
<ActiveGrid<User>
columns={columns}
data={users}
{...}
/>import { ActiveGrid } from '@workspace/active-grid';
type Product = {
id: string;
name: string;
price: number;
};
// Generic type parameter
<ActiveGrid<Product>
columns={columns}
data={products}
onRowClick={(row) => {
// row is typed as Product
console.log(row.name);
}}
/>import { GridColumnDef } from '@workspace/active-grid';
const columns: GridColumnDef<Product>[] = [
{
accessorKey: 'name', // ✓ Type-safe: 'name' exists on Product
header: 'Product Name',
},
{
accessorKey: 'invalid', // ✗ Error: 'invalid' doesn't exist
header: 'Invalid',
},
];TypeScript can infer types from your data:
const users = [
{ id: '1', name: 'John', email: 'john@example.com' },
{ id: '2', name: 'Jane', email: 'jane@example.com' },
];
// Type is inferred as Array<{ id: string; name: string; email: string }>
<ActiveGrid
columns={columns}
data={users}
{...}
/>But explicit types are recommended:
type User = {
id: string;
name: string;
email: string;
};
const users: User[] = [
{ id: '1', name: 'John', email: 'john@example.com' },
];
<ActiveGrid<User>
columns={columns}
data={users}
{...}
/><ActiveGrid<User>
onRowClick={(row: User) => {
console.log(row.id); // ✓ Type-safe
console.log(row.name); // ✓ Type-safe
console.log(row.invalid); // ✗ Error
}}
{...}
/>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);
}}
{...}
/>{
accessorKey: 'name',
header: 'Name',
cell: ({ row, getValue }) => {
// row.original is typed as User
const user = row.original;
// getValue() returns the correct type
const name = getValue() as string;
return <div>{user.name}</div>;
},
}{
accessorKey: 'age',
header: 'Age',
cell: ({ getValue }) => {
const age = getValue() as number;
return <span>{age} years old</span>;
},
}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}
{...}
/>import { useActiveGrid } from '@workspace/active-grid';
import { Table } from '@tanstack/react-table';
function TableControls() {
const table = useActiveGrid<User>();
// All methods are type-safe
const selectedRows = table.getSelectedRowModel().rows;
const selectedData: User[] = selectedRows.map(row => row.original);
return <div>{selectedData.length} users selected</div>;
}<ActiveGrid<User>
toolbar={{
actions: (table: Table<User>) => (
<Button onClick={() => {
const rows = table.getSelectedRowModel().rows;
const users: User[] = rows.map(r => r.original);
handleBulkAction(users);
}}>
Process Selected
</Button>
),
}}
{...}
/>import { Row } from '@tanstack/react-table';
function handleRowAction(row: Row<User>) {
const user = row.original; // Typed as User
console.log(user.name);
}
{
id: 'actions',
cell: ({ row }) => (
<Button onClick={() => handleRowAction(row)}>
Action
</Button>
),
}import { Column } from '@tanstack/react-table';
type CustomHeaderProps<TData> = {
column: Column<TData>;
title: string;
};
function CustomHeader<TData>({ column, title }: CustomHeaderProps<TData>) {
return (
<div onClick={() => column.toggleSorting()}>
{title}
</div>
);
}
// Use with specific type
{
header: ({ column }) => (
<CustomHeader<User> column={column} title="Name" />
),
}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, // User[]
totalCount: response.total,
};
};
<ActiveGrid<User>
mode="server"
fetchFn={fetchUsers}
{...}
/>import { ContextMenuItem } from '@workspace/active-grid';
<ActiveGrid<User>
contextMenu={(row: User): ContextMenuItem<User>[] => [
{
label: 'Edit',
action: (user: User) => handleEdit(user),
icon: Pencil,
},
{
label: 'Delete',
action: (user: User) => handleDelete(user),
icon: Trash,
variant: 'destructive',
},
]}
{...}
/>import { GridRowMenuConfig } from '@workspace/active-grid';
const rowMenuConfig: GridRowMenuConfig<User> = {
enabled: true,
customItems: (user: User) => (
<DropdownMenuItem onClick={() => handleAction(user)}>
Custom Action
</DropdownMenuItem>
),
};
<ActiveGrid<User>
rowMenu={rowMenuConfig}
{...}
/>type Status = 'active' | 'pending' | 'inactive';
type User = {
id: string;
name: string;
status: Status;
};
{
accessorKey: 'status',
header: 'Status',
cell: ({ getValue }) => {
const status = getValue() as Status;
// TypeScript knows status is one of the union types
return <Badge>{status}</Badge>;
},
}type Address = {
street: string;
city: string;
country: string;
};
type User = {
id: string;
name: string;
address: Address;
};
{
accessorKey: 'address.city', // Nested access
header: 'City',
cell: ({ row }) => {
const address = row.original.address; // Typed as Address
return <div>{address.city}</div>;
},
}import { getSelectionColumn, getRowNumberColumn } from '@workspace/active-grid';
const columns: GridColumnDef<User>[] = [
getSelectionColumn<User>(),
getRowNumberColumn<User>({ header: '#' }),
{
accessorKey: 'name',
header: 'Name',
},
];import { CellContext } from '@tanstack/react-table';
type UserCellContext = CellContext<User, unknown>;
function CustomCell({ getValue, row }: UserCellContext) {
const value = getValue();
const user = row.original; // Typed as User
return <div>{user.name}</div>;
}type EditableUser = User & {
isEditable: boolean;
};
<ActiveGrid<EditableUser>
enableRowSelection={(row) => row.isEditable}
{...}
/>import { useState, useMemo } from 'react';
import {
ActiveGrid,
GridColumnDef,
getSelectionColumn,
EditingCommitPayload,
} from '@workspace/active-grid';
import {
RowSelectionState,
SortingState,
ColumnFiltersState,
Table,
} from '@tanstack/react-table';
// Define data type
type Product = {
id: string;
name: string;
price: number;
category: string;
inStock: boolean;
};
function ProductsTable() {
// Typed state
const [products, setProducts] = useState<Product[]>([]);
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [sorting, setSorting] = useState<SortingState>([]);
const [filters, setFilters] = useState<ColumnFiltersState>([]);
// Typed columns
const columns = useMemo<GridColumnDef<Product>[]>(() => [
getSelectionColumn<Product>(),
{
accessorKey: 'name',
header: 'Product',
},
{
accessorKey: 'price',
header: 'Price',
cell: ({ getValue }) => {
const price = getValue() as number;
return `$${price.toFixed(2)}`;
},
},
{
accessorKey: 'category',
header: 'Category',
},
{
accessorKey: 'inStock',
header: 'In Stock',
cell: ({ getValue }) => {
const inStock = getValue() as boolean;
return inStock ? '✓' : '✗';
},
},
], []);
// Typed callbacks
const handleRowClick = (product: Product) => {
console.log('Clicked:', product.name);
};
const handleDataCommit = async (payload: EditingCommitPayload<Product>) => {
await api.updateProduct(payload.rowId, payload.changes);
};
return (
<ActiveGrid<Product>
columns={columns}
data={products}
getRowId={(row) => row.id}
state={{
rowSelection,
sorting,
columnFilters: filters,
}}
onRowSelectionChange={setRowSelection}
onSortingChange={setSorting}
onColumnFiltersChange={setFilters}
onRowClick={handleRowClick}
onDataCommit={handleDataCommit}
toolbar={{
actions: (table: Table<Product>) => {
const selected = table.getSelectedRowModel().rows;
const products: Product[] = selected.map(r => r.original);
return (
<Button onClick={() => handleBulkDelete(products)}>
Delete {products.length} products
</Button>
);
},
}}
/>
);
}