Careeroza
Generic Components
A Reusable Typed List or Table
Components can be generic too. A DataTable<T> infers T from the rows prop, so column keys and render functions are checked against the row type.
import type { ReactNode } from "react";
type Column<T> = {
key: keyof T;
header: string;
render?: (row: T) => ReactNode;
};
type DataTableProps<T> = {
rows: T[];
columns: Column<T>[];
getRowId: (row: T) => string | number;
};
export function DataTable<T>({ rows, columns, getRowId }: DataTableProps<T>) {
return (
<table>
<thead><tr>{columns.map((c) => <th key={String(c.key)}>{c.header}</th>)}</tr></thead>
<tbody>
{rows.map((row) => (
<tr key={getRowId(row)}>
{columns.map((c) => <td key={String(c.key)}>{c.render ? c.render(row) : String(row[c.key])}</td>)}
</tr>
))}
</tbody>
</table>
);
}
// usage: T is inferred as Product
<DataTable
rows={products}
getRowId={(p) => p.id}
columns={[
{ key: "name", header: "Product" },
{ key: "price", header: "Price", render: (p) => "Rs. " + p.price },
// { key: "rating", header: "Rating" } -> error: "rating" is not a key of Product
]}
/>;Generic Arrow Components in .tsx
Note: In a .tsx file, const List = <T,>(props: ListProps<T>) => ... needs the trailing comma (or <T extends unknown>) so the parser does not read <T> as a JSX tag. A function declaration avoids the issue.