I am trying to create a reusable Table component in React, the idea being that you can pass in a list of columns, and the data to generate a table. Let's say the data was an array of people - Person[]
, I want to call something like this:
type Person {
name: string;
}
const people = [
{ name: "John Smith" }
];
const columns = [
{label: "Name", path: "name"}
];
return <Table<Person> data={people} columns={columns} />
So in the type definition of the columns array, the path
property should be a keyof the data type (i.e. Person
). Since Table is a reusable component, I am trying to achieve this with generic types and a keyof T
, however I am clearly missing something here as the TS compiler is telling me: Type 'string' is not assignable to type 'keyof Person'.
Here's the code for my Table component:
import TableBody from "./TableBody";
import TableHeader from "./TableHeader";
export interface Column<T> {
label: string;
path: keyof T;
key?: number | string;
finder?: () => string;
content?: (arg: T) => string | React.JSX.Element;
}
interface Props<T> {
data: T[];
columns: Column<T>[];
}
const Table = <T extends { id: number }>({ data, columns }: Props<T>) => {
return (
<div className="relative overflow-x-auto">
<table className="w-full text-left rtl:text-right text-gray-500">
<TableHeader<T> columns={columns} />
<TableBody<T> data={data} columns={columns} />
</table>
</div>
);
};
export default Table;
The type that is inferred for columns
is
{
label: string;
path: string;
}[]
because it would be annoying for array literals to infer strict types by default.
If you want it to infer a stricter type from the literals, you need to mark (parts of it) as const
. If you mark the whole columns
as const, then you will need to adjust the definition of Props
to be readonly
.
N.b. you are also missing id: number
on your definition of Person
.
interface Props<T> {
data: T[];
columns: readonly Column<T>[];
}
const Table = <T extends { id: number }>({ data, columns }: Props<T>) => {
return (
<div className="relative overflow-x-auto">
<table className="w-full text-left rtl:text-right text-gray-500">
<TableHeader<T> columns={columns} />
<TableBody<T> data={data} columns={columns} />
</table>
</div>
);
};
type Person = {
id: number;
name: string;
}
const people = [
{ id: 1, name: "John Smith" }
];
const columns = [
{label: "Name", path: "name"}
] as const;
const People = () => {
return <Table<Person> data={people} columns={columns} />
}