Supercharging Autocomplete and Typechecking for JSX Components with Generics!
Tags: typescript, react, webdev
Published: 2023-03-06 22:07:40

**TL;DR:** Skip to the [end of the article](#working-example) for a working Stackblitz implementation.

If you've spent any time writing functions and classes in TypeScript, you may have stumbled upon [generics](https://www.typescriptlang.org/docs/handbook/2/generics.html) which allow you to infer types being passed into a function or class without having to necessarily assign types ahead of time. What you may not have realised is that the same can be applied to JSX components.

Using generics allows JSX components to offer autocompletion and type safety for things that would otherwise have been typed as `any` due to the highly generic nature of components.

In this article, we're going to use [Preact](https://preactjs.com/) as our JSX library to render components, and we'll build a table rendering component that will infer and typecheck its props based on whatever data we give it.

## The Data Model

To start, we'll need to define the shape of data we'll be passing into the table component. To make things simple for the end-user, we'll start with a simple constraint — the data must be an array of objects whose properties are field identifiers:

```typescript
export type TableData<T extends Record<string, unknown>> = T[];
```
[Try this example in the TS Playground](https://www.typescriptlang.org/play?#code/KYDwDg9gTgLgBDAnmYcAqBDARgG2AEQxgwB4AlCAdzlBmADsATAZzjOAGNpGTmYoAlvQDmAGjgBXegGt6VegD4FcALxsqAbQC6AbgBQegPSH02PIWJw51UCg4xWGenAxQoGRHAgAzL1gBWnA56SChwALKIFhiqprgERKQA3nACjABcVhIAtljAUDpWGNnAmXyCInAAvgr6RiYC8FDA3hLMwKwwEAgY0qhy9AC0EAFBgzgCfQjIHSEzcACS9ABuGBOM0QCMsZjx0bz8QsIKc2FLq+vRAEw7ZgnEJPQ5eVAn9ZLtCAAWqLvmidMwl04BxmkRUDEuDg8PYBBBnN5oN9UMR4nouPQ+C5MpForENHo4ES4Ck0plNuJ6MVSnAAOQAIQwGNp1VEhOJpIycCulOpmQZwCczBZVT0uj0QA)

Because the keys can be associated with any value, table cells will need to be able to render values based on their type. For some values, the end user of the table component might want to control how that field renders its values by providing their own component. To facilitate this, let's create a type that will generate props for a component based on the row type given by the user:

```typescript
import type { ComponentType } from 'preact';

// a Field is any component that renders a value: <Field>{value}</Field>
export type Field<Value> = ComponentType<{ children: Value }>;

// we create an optional map of field name to component that renders that value
export type FieldRenderers<Row extends Record<string, unknown>> = {
  [Prop in keyof Row]?: Field<Row[Prop]>;
};
```
[Try this example in the TS Playground](https://www.typescriptlang.org/play?target=99&jsx=4&jsxImportSource=preact#code/PTAEAECsGcA8C5QCcCmBDAxgFwLQ1gFAgT4CSAtgA4D2SWAytQK5IYqKWqZYECWVtLKCwBPSilABvUAGFqAgHYoFWACpiJAX1AAzJPNAAiTumyGA3ASJg0oAGK8UAGwAmoXtFBoFI0Bnk0SirCABZoQqgKLihInrYAbmhOTOygADwOzi4AfJKJySiaacCZrtkEKLA0dMIa9o6uaQBqSSnZoAC8sgHUQWoaadIYIbyukYgtBaCa2ZbWoADuEhhcWBLeoNSUWLy9SaDkaJSbOroNbgpo5BJY1H49faHhyMrRsU9C+SkVVYK14vUsgAlV4xGLQNJA6gLUCVNZRTwg-xIFxpaBYJC8BQAcwANKAmAoANYKaEKbLtLqSAigUAAbQACvpjljQESUCJqKcoQsALoAfkQpVRPMZzN5swImksQA)


## The Components

Since we're building the table component from the ground up, let's start with the default field component:

```typescript
export const DefaultField: FunctionalComponent<{ children: any }> = ({
  children,
}) => {
  if (typeof children == 'number') {
    return <>{children.toLocaleString()}</>;
  } else if (typeof children == 'string') {
    return <>{children}</>;
  } else if (typeof children == 'boolean') {
    return <>{children ? '✅' : '❌'}</>;
  } else if (children instanceof Date) {
    return <>{new Intl.DateTimeFormat().format(children)}</>;
  }
  return <>{children}</>;
};
```
[Try this example in the TS Playground](https://www.typescriptlang.org/play?target=99&jsx=4&jsxImportSource=preact#code/PTAEAECsGcA8C5QCcCmBDAxgFwLQ1gFAgT4CSAtgA4D2SWAytQK5IYqKWqZYECWVtLKCwBPSilABvUADEmAO2y9q8tABsAwtQHyU8oQF9QAMyTbQAIk7psFgNwECKWDTqgMK6EIAiKY2iY1LBleFDUAE0Q5RSxlVU1tGl19AB5pDAALXgjUeUQ0eRFQAwA+UABeUAAKSQJQdyycvQAaAgMASgqy2vreY2rRcWp+zOzw3IrKgHJ5JnIAIxQkKc6e+uQULBZ5UBSSyVGm+QA6LGoAGWoMdRR6LCReeQBzKvaDFOASh3qjMOgJPoDMQoYYNMYTcrTLwPZ4rKR1daoLZIHZ7A6NcZ6d6fb7FUB-AH9KqDEEjDEQ6bzajUNToeRwtb1JHbXb7Q6YnYAflAU0AoORTUCIKaAGXIptivgjfmp-qBAVV2RNHl4CmxQd40FgUKsEUzNiy0boAO6gUj6NTHdWagAq-BQMlo5A1r2OxgdToVejeHwlPwRzJRrPR4Kx3ocBgcQA)

This component takes a couple of common types like numbers, strings, dates and booleans and renders them according to their default browser locales to nicely format them. It doesn't try to cover all possible types, but enabling the user to provide a custom renderer for a field will take care of that problem. If the value itself is a JSX element, it will also just render the value.

The next step is to create the table component that will tie all of these types together. Let's start with the component signature itself:

```typescript
export const Table = <Row extends Record<string, any>>({
  data,
  fields = {},
  columnOrder,
}: TableProps<Row>) => {
  /* ... implementation detail in TS playground ... */
};
```
[See the full implementation in the TS Playground](https://www.typescriptlang.org/play?target=99&jsx=4&jsxImportSource=preact&ssl=42&ssc=1&pln=75&pc=3#code/PTAEAECsGcA8C5QCcCmBDAxgFwLQ1gFAgT4CSAtgA4D2SWAytQK5IYqKWqZYECWVtLKCwBPSilABvUAGFqAgHYoFWACpiUAGlAAxJguy9qCtABs5i5UIC+oAGZJ5oAESd02ZwG4CRMGl28KKYAJqC80KBoCiKgGPI0SirCABZoQqgKwShIEf4AbmZM7KAAPDqBIQB8kgWmRdYlwOVBwZUEKLA0dMIaAS0lAGqFKJWgALyy8cZW6uIl0hjJvCEZiEN1EtaV3r6gAO4SGFxYElGg1JRYRiamoORolOd29hWhJuQSWNSxU4lCWKl0sosjkUmlQLUiu1OoIeuI+iEAErA7LZaAlRHUPagDonTIRZFxJDBErQLBIXgKADm2n0AGsFFiFJVRhNJARQKAANoABUcj0poDpKBE1GemL2AF0APyIZohDFY3n8yXbAjWHYdLpCOIKMmgAAiKDsaCYpiw8uCcv0hmMZgsCSs81iSxWykQURiW3GoAAFOzOYtlsEMpp1QBKcajANhZ6+0TiMUu4MZcYTADkCiY5AARtl05GY5zUFgWApStUg26FAA6L4AGWoGDMKHo5MpVN94YawDVnNsQWgEl4cYTKCTVZDyjToHTZIp1ILUg5nOQKFLSHLJUrrqnCh7fdAA9MQ9jfrHE93qbGGZz1GopnQCiXRbXG63O5T0+ls8AoOTp0BEHTQAZcnTA9vH7HET2HONJ1TSkySiNgkwNNIUELFdi3XMsK0kJRsVIFRTBrVCTlUfgUB0Wh7iwLsazsai0l9ODlG7RpD2sFcSxw7dJBY-d2O8DUfDHUBVDQHNHz5C50QlHFYDxYICRQIkSXnDtaQUBkmRZH0Y2AAAqAyxOSCRgjSfwvlASg0GgCIrIBT4JMfUADOAFdzKwNBEAlLlJQg0BDOMgB5S5rjMO4HieH46nIct3hQezvjiSwVHswE10ybJIghYZXPczk7FeaBZQRYJkSy1AckVPZDyC0AAEFTFMLEIl1ckH1MDsUgkWgQWiuJYr1TKQRQUJBUc4RnIkNyV0G7MFGC4lslK31hVFcUsXDPyhJ2XV9WbSheC8rqAC8JAmX150QdTqUjMY2k5edMNAGtUEoUxMBQJrTF9dMAH1020ACCxet6UA+r7fWAX1pRKMYAD0AB9QG2tAcFOyVgBpP0aMWe7Rjx5I62oABVShxCQGRbJQLtw01GFun2oRxMki7Sjk3FgWU1TSXbaltE9Fl-Q8iyw0K4q9OscWYoWpaQTDaxEFZqT+VkrFKgJ5dORHP1PLQGtH2pAEZwABkjbjN1KYJeDySoADlvn1mtGhtu2AuZ2W4oiCZ5ri+XsulH9gpzSAVKwGt1ugX19a5U3JXprjsKt30XpKLy2diT67LGZx9ZVlBnEe1dOXT0y0FaF6S-TpBi5Lku+IfBboBre5KF9Zim7irXU-rvvSgBSsHmOsxeHOzuhpJtsF07cM2OAQeq-rufOP70oF9rpfGkciu69XdO72CEQ94b52247xw9h7pfq-JE-+8boaW-PieFq11818DYx9Qq-qJiKlo0AuR+wUJKUAQdDTGlNOaS0AVP6rk9pCdml9gFd1AXA+Bb4cK90wbfSuuC+4YhRLXGowwey-2yPfeB298EEMTrg6w3Yb77w3lQ1GTDCEL0PsfNOC9prF0ThqIAA)

You'll notice that we used a generic parameter called `Row` to declare a type parameter that we use to pass to the `TableProps` type. This allows the component to infer the type based on the array being passed into the `data` prop when the component is used.

Once the data type for `data` is known from its usage, the rest of the props are adjusted accordingly; `fields` now only accepts a partial object whose keys match keys in the `data` array items and `columnOrder` takes an array of keys. If you try to add other strings or provide other keys, you'll get a type error. Same goes for the cell renderers; it expects a class or functional component that takes `children` and renders it.

## Using the `Table` Component

Now that we've written the `Table` component, it's time to try out the neat features generics give us as a user.

First, let's define some data without explicitly defining a type for it:

```typescript
const exampleData = [
  {
    id: 1,
    name: 'Johnny Burger',
    email: 'johnny@example.org',
    bio: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce viverra malesuada dui. Vivamus ac aliquet justo, eu laoreet purus. Aliquam pulvinar, nulla ut rhoncus viverra, purus ligula tincidunt orci, a venenatis lacus libero eget urna. Vivamus dictum nunc id ligula iaculis, ac tincidunt turpis auctor. Fusce mollis viverra diam nec hendrerit. Fusce iaculis sed nisl ut cursus. Fusce dapibus mi id lacus dapibus, vel semper mauris rutrum. Maecenas mattis tincidunt leo non pulvinar. Maecenas nibh nulla, dapibus quis est sit amet, accumsan viverra sem. Fusce a fringilla lacus. Maecenas iaculis justo non finibus finibus. Quisque aliquam ultrices lacus eget porta.',
  },
  {
    id: 2,
    name: 'Henrietta Biggles',
    email: 'beagle@example.org',
  },
];
```
[Try this example in the TS Playground](https://www.typescriptlang.org/play?jsx=4&jsxImportSource=preact#code/MYewdgzgLgBApgDwIYFsAOAbOARJUkwC8MA2gFAwwDeFlMAlgCYBcMAjADS2VipysByAFIgAFmDABPGACEArgCcA5nAUCudeCiT0MggFZiJkgAKJUmOADoQy9dxgAjeiEEAZW3BQM0EOd8YQDFsYCHpYPigOGFBIOGAoOChFGCRGejR6CGB6MCV4DHCrGAAxOWy4GAA3eirVBQJtLD80gkY5emKANVrUctTgVMKARzkkmH1yqBBouDkYDCRPcbRFcuKAQRG5VBhVjBreBWiwOQxFmDlYBVFwYH6auoUG6NWFfsKlM4IoXJz2sCwWw5aIEOpgOC8X4QBZIe4wwqOVQgeAqWCKXjdXoofrpBL+GCnMCDJgLehfC70OFnLKgwa-YlMOSAmDJBSZGFIOQJWzFMoVGAoIKFGGPeptKneCGDUSQxgKVRFUrlYCVKn3EWhOCMQlZDCXWD3BR+CB8lWVRhITKOfooegMHWLeEwS3W8rROr6iBeNCqQVchRZGDvKDvFDFACySHikKQMO0UGhrL+TJZWBRYHAezOhyQCkj0dVvBhYHojlEhLOi2irrL-VGQbg0FC4VSKCSdPuKAgSDA1Vq4q14eVAoIADNA3ldBcnesYFGY8WGNTNZNoBms2PcnWYVvSzbTTAAIodCCjSpIba7M6h+iqhHUmFwNF7Wz4Kz2SgAXw01AcTFYAAmX8eD4QQAAlIUDJJ8FkcklGaT86C8HQ9BgAQkSQBC4DMZB0CwGw7F-H8yAAXQAbjIIA)

If you open the playground and hover over the type, you'll see that the inferred type is:

```typescript
const exampleData: ({
    id: number;
    name: string;
    email: string;
    bio: string;
} | {
    id: number;
    name: string;
    email: string;
    bio?: undefined;
})[];

// ...or simplified a bit:

const exampleData: {
    id: number;
    name: string;
    email: string;
    bio?: string;
}[];
```

If we pass this data to the `Table` component, nothing notable happens other than rendering the table:

```typescript
import { render } from 'preact';
import { Table } from './table';

const exampleData = [
  {
    id: 1,
    name: 'Johnny Burger',
    email: 'johnny@example.org',
    bio: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce viverra malesuada dui. Vivamus ac aliquet justo, eu laoreet purus. Aliquam pulvinar, nulla ut rhoncus viverra, purus ligula tincidunt orci, a venenatis lacus libero eget urna. Vivamus dictum nunc id ligula iaculis, ac tincidunt turpis auctor. Fusce mollis viverra diam nec hendrerit. Fusce iaculis sed nisl ut cursus. Fusce dapibus mi id lacus dapibus, vel semper mauris rutrum. Maecenas mattis tincidunt leo non pulvinar. Maecenas nibh nulla, dapibus quis est sit amet, accumsan viverra sem. Fusce a fringilla lacus. Maecenas iaculis justo non finibus finibus. Quisque aliquam ultrices lacus eget porta.',
  },
  {
    id: 2,
    name: 'Henrietta Biggles',
    email: 'beagle@example.org',
  },
];

render(
  <Table data={exampleData} />,
  document.body
);
```
[Try this example in the TS Playground](https://www.typescriptlang.org/play?target=99&jsx=4&jsxImportSource=preact&ssl=94&ssc=3&pln=77&pc=1#code/PTAEAECsGcA8C5QCcCmBDAxgFwLQ1gFAgT4CSAtgA4D2SWAytQK5IYqKWqZYECWVtLKADeyFADsAJiiQAaUAGFqA8RKwAVAJ6UU8gGJNx2XtXFoANkpVrQAX1AAzJMtAAiTumyuA3ASJg0UD1eFHNJUF5oUDRxTVAMZRpVcSEsAAs0IVQpGSjAgDcLJnZQAB5g0MkAPmFC82LbUuAKsKqCFFgaOlAsbRQgkLDSgDUilCrQAF5FRNM1LR1S0Qw03jDsxFH6-tsq339QAHd+jC4sfpjQakosEzNzUHI0SiuHR0Hws3J+rGp42eSqQyWQk0iQUXSmVAdWK7U6gh6fQGlQASqCZLlSijqIdQB1zlIomiEkhJKVoFgkLxxABzeSGADW4hx4iqE2mwgIoFAAG0AArOF7U0AMlCaahvbGHAC6AH5EC0yVL+YLpXsCLZ9h0ukIEuIKaAACIoBxoJjmLCKhWGYymCxWJJqJbxVbrCSIGJxXZTUAACk53JWa0k2VkGoAlFMJgGIm9fb0dBKXcHslNpgBycRMcgAIxk6cjMe5qCwLHEZRqQbd4gAdL8ADLUDAWFD0SnUmm+8ONYDq7n2ULQfq8OMJlBJqshiRp0DpilU2kFkRc7liUtIculSuuqfiHt9ux48xD2N+scTnepyYZnPUajmdDiJdFtdlivCSep2WzwCg5OnQIg6aADLk6b7r4-ZHieI5+p+07UhSMRsEmhqZCghYrsWKDrpuNSqLipApOYNYoec6j8Cgei0E8WBdjWDhUZkvqweI3ZNAetgriWb5bh+l4SGBGr7GOoDqGgOYPgK1zQFiOJ4rABKSESKAkmS84dvS4hMiybI+jGwAAFT6SJaT9JImSBL8oCUGg0AQn86Q-GJD6gPpwArmZWBoIgyrSuBoAGUZADyNx3BYjzPK8-z1OQ5ZfCgdn-NYKQQsCYg5Eg0TQmMLludyDgfNA8rImEaLpZiUoHgFoAAILmOYOJRHqlL3uYHY9CZVykjIkUJNF+ppWCKDhMKDk9E5-SuSuvXZuIgVdUgRW+qK4qSji4Y8r5gl+HqBrNpQvCea1ABe-TTL686IGptKRpMbTcvOGGgDWqCUOYmAoLV5i+umAD66byP+BaPc9KCve9vrAL6sqlJMAB6AA+oDrWgOBHdKwB0n61ErDdEzY2kdbUAAqpQOhIAoNkoF24ZavC3Q7UIoniadZRSnJClKSp5LtrS8iemy-rueZYZ5QVum2CLUUzXNYJhrYiBMxJgrSRVuPLty0G+h5aA1g+tLpDOAAMkZcRuZSSLw+RVAAcn82s1k0FtW35DNSzFUTTNNMUy91srfoFOaQMpWA1st0Ba+ZPKG9KNOcVhb6+o9pSecz8RvbZkyuNrisoK4d2rtyycmWg1SPQXydIPnBcFx+94zdANZPJQvpMXXMVq4n1dd2U6SVs8B0WLwJ2t31hNtgunbhqxwC92X1dTxx3dlDPldz00Dkl1Xq7J7ekiaFvNf203LfOIcHdz+XlIH93td9Q3x8jzNasvkvgamAapVgj6+WVNAPJe+IaUoA-ZGhNGaC0io-Kv1XK7GELNT7-zboAqB0DXxm07qgy+pdMFdyxOiSutQxg9k-jIa+0D17YJwbHTBthuwX23ivMhSM6G4JnrvfeScZ7jXzrHTU2135CA6GgKgD4SKBGmDyFcL5eCSEQAARkltyOKgEABS1A0jiFiKAAAQiwGk+ZFF4ieGsQCkB1GaM0OAIRIiUA1loDSf6j0cwmEAo2VA5AIiUGgNmUAkh7y0FANAA60RvhYHkDtYO8cMol14PtaAGA2qhAOjWIITB4n9HyJbGQSBAhPAfN4kugRJBMF4Ck4YlthFpOiBgaIrUACOxQhCQDSb8eQKAmCgDerQFAWErIsDSSk2qvAGnCL6eYTJZg5CgCzHVQITAshpFMBgKpmT8jZLQPISg-SoitRpOaCy1IEnFJSJ1BJfNoQSAkJkSInTMBVNanmZweJ9FCDLDrUA5TCjkCqRbbAPisxGAiOEXZ+yIh3NatAPmNTbhGBkYYVILA4nRCYNgWgKSDDpMeC1G5qz1m+N4KM1QNSTJSFQFSEOqTMUEuWRCwJQ1pmRAePM+ILBvEN0pWwXx-ccxVPILwIFtzllRDMvtHlkKLkPCHFQbqTwWA3KQPMhV5AUkAFk0DKSuVEaitwISHLhSch8fxmTli2eM6kaAkCqvVWwMwURxC8BzGkaZ5o3ryBFQ6qpDSbnxSEEEoQwisJQuWeQaAlxcVIByXS5VHKLiOAnmsN6gqBmgDVRq21YKaU3OaRSI1ph3j2rFfmj17KACKJToANIuPUpgozzTtjYDsu5UQUAvKsoIHWjj+yS2kbI0AAAmQxyjZwAAkJBUiwp5HRvAaQ0nyZ21cKBjHmEAnmNAs6UBWNgMI16tj7HzolgQTaBBshggwaUHOXLPKTGENYndYj7C9kln44NagazsIIDTIAA)

And here's where all that effort starts to pay off. We want to do two things:

1. We want emails to be linked using the `mailto:` protocol for easy access; and
2. We want to truncate the `bio` field if it is longer than 50 characters.

Let's create two components that will do this for us:

```typescript
const Email: FunctionalComponent<{ children: string }> = ({ children }) => (
  <a href={`mailto:${children}`}>{children}</a>
);

const Truncate: FunctionalComponent<{ children: string | undefined }> = ({
  children = '',
}) => <>{children.length > 50 ? `${children!.slice(0, 50)}…` : children}</>;
```

Let's use these components to render the values in the two fields. If we now use the `fields` prop on the `Table` component, we'll get autocomplete that suggests the field names from the example data!

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v5ligwrumfcfeyhjopgc.png)
[Try this in the TS Playground](https://www.typescriptlang.org/play?target=99&jsx=4&jsxImportSource=preact&ssl=103&ssc=5&pln=100&pc=3#code/PTAEAECsGcA8C5QCcCmBDAxgFwLQ1gFAgT4CSAtgA4D2SWAytQK5IYqKWqZYECWVtLKADeyFADsAJiiQAaUAGFqA8RKwAVAJ6UU8gGJNx2XtXFoANkpVrQAX1AAzJMtAAiTumyuA3ASJg0UD1eFHNJUF5oUDRxTVAMZRpVcSEsAAs0IVQpGSjAgDcLJnZQAB5g0MkAPmFC82LbUuAKsKqCFFgaOlAsbRQgkLDSgDUilCrQAF5FRNM1LR1S0Qw03jDsxFH6-tsq339QAHd+jC4sfpjQakosEzNzUHI0SiuHR0Hws3J+rGp42eSqQyWQk0iQUXSmVAdWK7U6gh6fQGlQASqCZLlSijqIdQB1zlIomiEkhJKVoFgkLxxABzeSGADW4hx4iqE2mwgIoFAAG0AArOF7U0AMlCaahvbGHAC6AH5EC0yVL+YLpXsCLZ9h0ukIEuIKaAACIoBxoJjmLCKhWGYymCxWJJqJbxVbrCSIGJxXZTUAACk53JWa0k2VkGoAlFMJgGIm9fb0dBKXcHslNpgBycRMcgAIxk6cjMe5qCwLHEZRqQbd4gAdL8ADLUDAWFD0SnUmm+8ONYDq7n2ULQfq8OMJlBJqshiRp0DpilU2kFkRc7liUtIculSuuqfiHt9ux48xD2N+scTnepyYZnPUajmdDiJdFtdlivCSep2WzwCg5OnQIg6aADLk6b7r4-ZHieI5+p+07UhSMRsEmhqZCghYrsWKDrpuNSqLipApOYNYoec6j8Cgei0E8WBdjWDhUZkvqweI3ZNAetgriWb5bh+l4SGBGr7GOoDqGgOYPgK1zQFiOJ4rABKSESKAkmS84dvS4hMiybI+jGwAAFT6SJaT9JImSBL8oCUGg0AQn86Q-GJD6gPpwArmZWBoIgyrSuBoAGUZADyNx3BYjzPK8-z1OQ5ZfCgdn-NYKQQsCYg5Eg0TQmMLludyDgfNA8rImEaLpZiUoHgFoAAILmOYOJRHqlL3uYHY9CZVykjIkUJNF+ppWCKDhMKDk9E5-SuSuvXZuIgVdUgRW+qK4qSji4Y8r5gl+HqBrNpQvCea1ABe-TTL686IGptKRpMbTcvOGGgDWqCUOYmAoLV5i+umAD66byP+BaPc9KCve9vrAL6sqlJMAB6AA+oDrWgOBHdKwB0n61ErDdEzY2kdbUAAqpQOhIAoNkoF24ZavC3Q7UIoniadZRSnJClKSp5LtrS8iemy-rueZYZ5QVum2CLUUzXNYJhrYiBMxJgrSRVuPLty0G+h5aA1g+tLpDOAAMkZcRuZSSLw+RVAAcn82s1k0FtW35DNSzFUTTNNMUy91srfoFOaQMpWA1st0Ba+ZPKG9KNOcVhb6+o9pSecz8RvbZkyuNrisoK4d2rtyycmWg1SPQXydIPnBcFx+94zdANZPJQvpMXXMVq4n1dd2U6SVs8B0WLwJ2t31hNtgunbhqxwC92X1dTxx3dlDPldz00Dkl1Xq7J7ekiaFvNf203LfOIcHdz+XlIH93td9Q3x8jzNasvkvgamAapVgj6+WVNAPJe+IaUoA-ZGhNGaC0io-Kv1XK7GELNT7-zboAqB0DXxm07qgy+pdMFdyxOiSutQxg9k-jIa+0D17YJwbHTBthuwX23ivMhSM6G4JnrvfeScZ7jXzrHTU2135CA6GgKgD4SKBGmDyFcL5eCSEQAARkltyOKgEABS1A0jiFiKAAAQiwGk+ZFF4ieGsQCkB1GaM0OAIRIiUA1loDSf6j0cwmEAo2VA5AIiUGgNmUAkh7y0FANAA60RvhYHkDtYO8cMol14PtaAGA2qhAOjWIITB4n9HyJbGQSBAhPAfN4kugRJBMF4Ck4YlthFpOiBgaIrUACOxQhCQDSb8eQKAmCgDerQFAWErIsDSSk2qvAGnCL6eYTJZg5CgCzHVQITAshpFMBgKpmT8jZLQPISg-SoitRpOaCy1IEnFJSJ1BJfNoQSAkJkSInTMBVNanmZweJ9FCDLDrUA5TCjkCqRbbAPisxGAiOEXZ+yIh3NatAPmNTbhGBkYYVILA4nRCYNgWgKSDDpMeC1G5qz1m+N4KM1QNSTJSFQFSEOqTMUEuWRCwJQ1pmRAePM+ILBvEN0pWwXx-ccxVPILwIFtzllRDMvtHlkKLkPCHFQbqTwWA3KQPMhV5AUkAFk0DKSuVEaitwISHLhSch8fxmTli2eM6kaAkCqvVWwMwURxC8BzGkaZ5o3ryBFQ6qpDSbnxSEEEoQwisJQuWeQaAlxcVIByXS5VHKLiOAnmsN6gqBmgDVRq21YKaU3OaRSI1ph3j2rFfmj17KACKJToANIuPUpgozzTtjYDsu5UQUAvKsoIHWjj+yS2kbI0AAAmQxyjZwAAkJBUiwp5HRvAaQ0nyZ21cKBjHmEAnmNAs6UBWNgMI16tj7HzolgQTaBBXYAFEl3WiMLcO0lgAROmWHxcQl0eY0jsOyP096UzTloVGP0K5SiBDSKgBwkxhAAAMl2-HgAAEl4p+vcoHdiwerD2NAbRY7HoESJBVRhUIXttPcB0cwUjOmYk+ieoBEaGGkPlVQ4RvRnRjMxH06ZHHftuu+ZiusJA0gNhMAArIbYBoBQMweYgAQhrNAVqbBfSG3kAJ7sgAyAlAwBZMyG2L7GyGCDBpQc6PW1iB6xO6xGL1XD-MI0AQOiEXWgExoAz22YeLYUzvZJZ+ODWoGs7CCA0yAA)

The same also applies for the `columnOrder` which constrains the array of strings passed to only contain keys from the data passed to the component.

## Working Example

To wrap things up, I've put together a working Stackblitz example where you can play around with the developer experience of using the component itself. Can you think of other examples that could benefit from type inference? Leave a comment!

{% embed https://stackblitz.com/edit/vitejs-vite-q89vzt?embed=1&file=src/app.tsx %}