React Generic Components
Tags: react, typescript, generics
Published: 2023-08-01 03:42:17

Some time ago, I came across the task of creating a table component for one of the applications I work on. Although it's a simple task, there are many opportunities when we talk about Developer Experience.

Let's start with something very simple, a component that takes two properties: `data` and `columns`.

```tsx
function TableHead(props: Pick<Props, 'columns'>) {
  return <>...</>
}

function TableBody(props: Props) {
  return <>...</>
}

export function Table({ data, columns }: Props) {
  return (
    <table>
      <TableHead columns={columns} />
      <TableBody columns={columns} data={data} />
    </table>
  )
}
```

I'll set aside the implementation of the component logic itself to make it easier to focus on what really matters for this post.

The `data` property here refers to the data that our table needs to render. To simplify things, we will consider a simple table that accepts `string`, `number`, or `boolean` as possible values, for example.

```ts
[{
  name: 'Pedro Henrique Pires',
  age: 27,
  github_url: 'http://github.com/pedrohenriquepires'
}]
```

As for the `columns` property, it refers to the mapping of the columns in our table. We might want to have `GitHub URL` written in the header of our table instead of `github_url`.

For this purpose, we can define that our column object will have two properties: `title` and `accessor`, where `title` is the property that sets the column name, and `accessor` is the property that defines which property of the data object this column will render.

For typing this component, we can use something like this:

```ts
type TableData = Record<string, string | number | boolean>

type TableColumn = {
  title: string
  accessor: string
}

type Props = {
  data: TableData[]
  columns: TableColumn[]
}
```

With this, we already have a properly typed component... do we?

Right away, it's possible to notice a problem that could potentially cause bugs in our component.

The `accessor` property of our column doesn't have any awareness of the properties that exist in our data, meaning it's very easy for someone to make a typing mistake and write `gtihub_url` instead of `github_url`, causing a failure in rendering that column.

![Image showing that no error occurs when trying to use the property gtihub_url instead of github_url.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kahowd41i7okwai4ru41.png)

Not to mention that we don't have any autocomplete assistance when configuring the columns, making it a bit more challenging for those using the component.

Fortunately, there is a solution to these problems, and it's much simpler than it may seem.

## Generic Types

With a few small modifications to our code, we can turn our component into a generic component and leverage TypeScript's inference to our advantage.

```ts
type TableData<T> = {
  [Property in keyof T]: string | number | boolean
};

type TableColumn<T> = {
  title: string
  accessor: keyof T
}

type Props<T> = {
  data: TableData<T>[]
  columns: TableColumn<T>[]
}
```

We changed the type `TableData` to receive a generic type and used this type for typing the properties of the object.

We also changed the type `TableColumn` to receive a generic type, using the properties of this generic type (keyof) as possible values for our `accessor` property.

The type `Props`, which we use to define the properties of our component, was also changed to receive a generic type, and this type is passed to the `TableData` and `TableColumn` types.

To finish, we also need to modify our component:

```tsx
function TableHead<T>(props: Pick<Props<T>, 'columns'>) {
  return <></>
}

function TableBody<T>(props: Props<T>) {
  return <></>
}

export function Table<T>({ data, columns }: Props<T>) {
  return (
    <table>
      <TableHead columns={columns} />
      <TableBody columns={columns} data={data} />
    </table>
  )
}
```

The change here is also simple; we just added a generic parameter to the component and passed that parameter to the `Props` type.

Now, the `accessor` property has awareness of the properties available within `data`. We will start to have autocomplete to assist in using the component and errors when using properties that do not exist, preventing bugs in our application.

![Image showing autocomplete with possible properties](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1x0fkmn923uz9wne6quh.png)

![Image showing an error when trying to use the property gtihub_url instead of github_url](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/178alhkb7lllza5sfxz8.png)

## Conclusion
With a few changes, we've managed to enhance the development experience and make our component more secure. If you're not yet familiar with generics, I strongly recommend checking out some materials on the topic. [This video](https://www.youtube.com/watch?v=dLPgQRbVquo) by Matt Pocock is an excellent guide to get started.

### Disclaimer
With great power comes great responsibility. Often, we can end up adding unnecessary complexity to our code. This article demonstrates just one use case and should not be taken as a rule.