is now decoupled from the data but we can take this further by decouping it from the shape of data. In it's current form it requires us to pass in data like this
The table structure is dynamic enough to accomodate any array of objects we pass in with the only required field being od id because we use it as the key to every row
this type T should be the type of the object that we pass in to the array so we can now replace type Player with T
tsx
1interfaceDynamicPlayerstableProps<T>{
2 columns:Array<TableColumn>;
3 data:Array<T>;
4}
our TbaleColumn type was also relying on the type Player so the type is also going to change to TableColumn<T> so we can now replace type Player with T
At this point typescipt will be able to give us auto complete for the coluns field based on what type T is based on the array we pass into the data field
with player rows array 
with teams row array 
 Out component is working fine from the outside but typescript is having a hader time understanding the types inside the component sine generice type T could be any type and it's keys could be numver|string|symbol , symbols aren't allowed as react keys so we can narrow that type by using an intersection & to inform typescrpt that the keys of T must be a string like we had in type Player
tsx
1typeTableColumn<T>={
2 label:string;
3 accessor:keyofT&string;
4};
5interfaceDynamicPlayerstableProps<T>{
6 columns:Array<TableColumn<T>>;
7 data:Array<T>;
8}
That resolves that issue but introduces another one where typescipt doesn't know what type T is and what type T[keyof T] is going to resolve to
Because in it's current shape bothe
tsx
1typePlayer={
2 id:number;
3 name:string;
4 age:number;
5 rank:string;
6};
7// and
8
9typeplayeyWithArrays={
10 id:Array<number>;
11 name:Array<string>;
12 age:Array<number>;
13 rank:Date;
14 ratio:{
15 numerator:number;
16 denominator:number;
17};
18};
>[!TIP]
any type can be passed into the component as type T , react and our table doesn't expect that and will throw an error if we try to render an array or Date object in a td or any react node . so to further specify what inputs we expect we can use the extends operator
>[!NOTE]
in typescript it's either used to inherit behaviour from a class or to mark a generic type as a subtype of another more specific type
tsx
1Textendsstring// can only accept strings
2Textends{}// can only accept objects
3Textends{id:number}// can only accept objects with a key `id` that is a number
4TextendsRecord<string,string|number>// can only accept objects with string or number keys
We'll use this to specify that only objects with string or number keys can be passed into the table
>[!NOTE]
Record<TKey, TValue> can be used to specify objects
now with this we can only pass in objects that have string or number keys
tsx
1const data =[
2{
3 id:1,
4 name:"John Doe",
5 age:30,
6 rank:"Gold",
7},
8];
9const baddata =[
10{
11 id:1,
12 name:"John Doe",
13 age:[30],
14 rank:newDate(),
15},
16];
before restrictions  after restrictions 
one last thing is rqeire type T to include a key id because it's going to be used as the key for the table rows
tsx
1typeGenericItem=Record<string,string|number>&{ id:string};// field of id:string required in passed in objects
This type will cause issues because field ratio is an object and we can't render it directly in the table because react can't render objects as its children
First the Record type should also intersect with an object type
tsx
1// now lets the value of the record to be an object
so how do we get the types to the dot separated values? this one is non trivial and one of the examples of the ugly typescript people hate. PossibleNestedUnions uses recursion to extract the dot separated keys to inputs of type nested object
example
typescript
1typeExampleType={
2 a:string;
3 b:{
4 c:number;
5 d:{
6 e:boolean;
7 f:{
8 g:string;
9};
10};
11};
12 h: Date;
13};
14
15typeNestedKeys1= PossibleNestedUnions<ExampleType,1>;// "a" | "b" | "h"
we'll get auto complete for all the possibly nested types 
Finally we can make the title and description of the table dynamic too because we can render more than just players with this component
tsx
1<GenericTableWithNestedFields
2title="Players Table"
3description="list of players without their stats"
26/* AND if you're building for a library in a monorepo: */
27// "composite": true,
28// "declarationMap": true,
29
30/* If NOT transpiling with TypeScript: */
31"module":"preserve",
32"noEmit":true,
33/* If your code doesn't run in the DOM: */
34// "lib": ["es2022"]
35/* If your code runs in the DOM: */
36"lib":["es2022","dom","dom.iterable"],
37// if you want to use JSX (react)
38"jsx":"react-jsx",
39
40"paths":{
41"@/*":["./src/*"]
42},
43
44},
45// only files under `src` will be transpiled
46"include":["src"],
47// exclude files under `dist` and `node_modules`
48"exclude":["dist","node_modules"]
49}
Our file will be in the src directory with an index.ts file as the main entrypoint
>[!NOTE]
Entrypoint is the file that is executed when the package is imported
example
typescript
1// my-table-package is the name of the package which is the default entrypoint
2import{ GenericTable }from"my-table-package";
entry points are defined in the package.json file
json
1{
2"name":"my-table-package",
3"version":"1.0.0",
4"type":"module",
5// our transipiled file will be in the `dist` directory
6"files":[
7"dist"
8],
9// our entrypoint is the `index.ts` file
10"main":"dist/index.js",
11// the exports field is a newer syntax for node 16+ that allows for multiple entrypoints
12"exports":{
13".":{
14"import":"./dist/index.js",
15"require":"./dist/index.cjs"
16},
17// we can also define exports for specific files
18"/styles":"./dist/index.css"
19},
20// peer dependencies are dependencies that we expect the user of the package to already have installed , if we're unsure that thy'll have it we put it i the dependencies field (it might lead to bigger final size)
21"peerDependencies":{
22"react":"^18.0.0 || ^19.0.0",
23"react-dom":"^18.0.0 || ^19.0.0",
24"tailwindcss":"^3.0.0 || ^4.0.0",
25"daisyui":" ^4.0.0 || ^5.0.0-beta"
26},
27// devDependencies are dependencies that are only used for development and don't get included in the final package
28// if uo have peer dependencies u can put them in dev dependencies
29"devDependencies":{
30"@arethetypeswrong/cli":"^0.17.3",
31"@changesets/cli":"^2.27.12",
32"@eslint/js":"^9.19.0",
33"@types/node":"^22.12.0",
34"@types/react":"^19.0.8",
35"daisyui":"5.0.0-beta.6",
36"eslint":"^9.19.0",
37"prettier":"^3.4.2",
38"react":"^19.0.0",
39"tailwindcss":"^4.0.1",
40"tsup":"^8.3.6",
41"typescript":"^5.7.3"
42}
43}
We'll build this package using tsup . we can use tsc (the typescript compiler) to build the package but that would require some changes
```json //tsconfig.json { /* If transpiling with TypeScript: */ "module": "NodeNext", "sourceMap": true, }
typescript
1
2which requires that all our import should end with the file extension name
3
ts import { GenericTable } from "./src/components/GenericTable"; // to be import { GenericTable } from "./src/components/GenericTable.js";
typescript
1which can be a bit much if we already have the code we want to publish
2to use tsup we just add a config
3
ts //tsup.config.ts import { defineConfig } from "tsup";
3if u're not using pnpm u can use `npm link` instead of`pnpm install`
4
5>[!WARNING]
6This will only work if you build yoour project locally where the package project also is,if you ad this to a projecct that get's build inCI or any other machnie it will not work
7
8to make it usable by everyone we publish it to npm using
sh npm publish
typescript
1
2>[!NOTE]
3 once at least one project installs your library it will be
4 undeletable from the npm registry
5
6### Extras
7- check exports with[are the types correct](https://github.com/arethetypeswrong/arethetypeswrong.github.io#readme)
8to chek if everything is being exported correctly