Trying to build on my very basic TypeScript knowledge, I decided to rebuild a project I built about a year ago using TypeScript. Trying to avoid the urge to put any
everywhere, I've done some learning and made a start by building a component that was used thought my project. The Table.
In my previous project, I had about 3 separate Table components that were all slightly different and I just shipped it to get something out there. I knew it was horrible but I did it anyway.
This time, I wanted to build something reusable, type-safe and something I'd be happy showing off. So here it is:
import React from "react";
interface TableProps<T> {
headers: string[];
data: T[];
renderRow: (item: T) => React.ReactNode;
}
const Table = <T,>({
headers,
data,
renderRow,
}: TableProps<T>): JSX.Element => {
return (
<table>
<thead>
<tr>
{headers.map((header) => (
<td key={header}>{header}</td>
))}
</tr>
</thead>
<tbody>
{data.map((item, index) => (
<tr key={index}>{renderRow(item)}</tr>
))}
</tbody>
</table>
);
};
export default Table;
Let's break it down. Firstly the interface. This describes the structure of the data used by the table. To make it reusable, the type of data can vary, therefore TypeScript generics need to be used. The data is an array of some type, T, the headers are just an array of strings, and I decided to create a renderRow function so I can pass in anything into the table rows which holds the shape of T
.
TableProps<T>
is like passing an argument into a function. We're saying if I want to use data: T[]
, I need to pass T
into the interface. This will set the type of data
to whatever the type is of the data we are passing in.
Similarly when defining the Table, we need to say that we are passing a generic type so we need to add <T,>
. The props are in the shape of TableProps and return a JSX element:
({
headers,
data,
renderRow,
}: TableProps<T>): JSX.Element => {...
That's basically the TypeScript part of it. This is how it's used:
<Table headers={headers} data={data} renderRow={renderRow} />
With renderRow defining the table rows, e.g.:
const renderRow = (user: User) => {
return (
<>
<td>{user.name}</td>
<td>{user.age}</td>
<td>{user.address}</td>
<td>{user.email.}</td>
</>
);
};
This way of rendering rows maps over the data we pass it, and create a table row containing the item that is passed into the renderRow function.
Hope that all makes sense!