I’m writing a function (to generate an HTML table) that takes two parameters: columns
and rows
.
The idea is that rows
is an array of objects which represents the table rows, and columns
is an object that defines which properties can be in the rows objects.
I have a type definition for rows
which takes a generic argument of columns:
type column = { key: string; label: string; }; type rows<T extends column[]> = { [key in T[number]["key"]]: string; };
And I use these types in my table-making function:
interface TableBodyProps<T extends column[]> { columns: T; tableRows: rows<T>[]; } const TableBody = <T extends column[]>({ columns, tableRows, }: TableBodyProps<T>) => { tableRows.forEach((row) => { columns.forEach((column) => { console.log(row[column.key]); }); }); };
But upon trying to access row[column.key]
I get an error:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'rows<T>'
I guess what’s happening is that column.key
just returns a string type, so it makes sense that I couldn’t use it to index. So I guess I need to tell typescript that the string I’m using is indeed a key of each row, but I thought that’s what I was doing with the generic parameters in the first place!
Advertisement
Answer
You’ll need to use
type column<K> = { key: K; label: string; }; type rows<T extends column<string>[]> = { [key in T[number]["key"]]: string; };
so that column["key"]
is not just string
but a specific type (K
). Then you can do
type TableBodyProps<K extends string, T extends column<K>[]> = { columns: column<K>[]; tableRows: rows<T>[]; } const TableBody = <K extends string, T extends column<K>[]>({ columns, tableRows, }: TableBodyProps<K, T>) => { tableRows.forEach((row) => { columns.forEach((column) => { console.log(row[column.key]); }); }); };
or just
type TableBodyProps<K extends string> = { columns: column<K>[]; tableRows: rows<column<K>[]>[]; } const TableBody = <K extends string>({ columns, tableRows, }: TableBodyProps<K>) => { tableRows.forEach((row) => { columns.forEach((column) => { console.log(row[column.key]); }); }); };
where rows<column<K>[]>
is just Record<K, string>
. The separate T
parameter is only useful if you want to do something else with the specific cell type, e.g. pass it to a callback that takes a concrete T
.