Problems compiling a generic component
Tags: react, reactgenerics, typescript
Published: 2024-02-03 16:13:36

I am attempting to implement a table using generics in Typescript. The code I am using is based on a table component provided at:

    https://www.bekk.christmas/post/2020/22/create-a-generic-table-with-react-and-typescript

I am uncertainn if the author of this code tested it before publishing it. Most of it appears to work, but there is a single compilation problem that I am hoping to get some insights on.

The test data provided is pretty straightforward:

```
    interface Cat {
      name: string;
      age: number;
      gender: string;
      color: string;
      activityLevel?: string; // optional, same as string | undefined
      favoriteFood?: string; // optional, same as string | undefined
    }

```
The main (Table) component, with props, is below. It is Table.tsx:

```
    import React from "react";
    import TableHeader from "./TableHeader";
    import TableRows from "./TableRows";

    export type ColumnDefinitionType<T, K extends keyof T> = {
        key: K;
        header: string;
        width?: number;
    }

    export type TableProps<T, K extends keyof T> = {
        data: Array<T>;
        columns: Array<ColumnDefinitionType<T, K>>;
    }

    const style = {
        borderCollapse: 'collapse'
    } as const

    const Table = <T, K extends keyof T>({ data, columns }: TableProps<T, K>): JSX.Element => {
        return (
            <table style={style}>
                <TableHeader columns={columns} />
                <TableRows
                    data={data}
                    columns={columns}
                />
            </table>
        );
    };

    export default Table;
```
	
The component design is pretty straightforward. The Table component has a header and body component.

The header component, TableHeader.tsx, is below:

```
    import React from "react";
    import {ColumnDefinitionType} from "./Table";

    type TableHeaderProps<T, K extends keyof T> = {
        columns: Array<ColumnDefinitionType<T, K>>;
    }

    const TableHeader = <T, K extends keyof T>({ columns }: TableHeaderProps<T, K>): JSX.Element => {
        const headers = columns.map((column, index) => {
            const style = {
                width: column.width ?? 100, // 100 is our default value if width is not defined
                borderBottom: '2px solid black'
            };

            return (
                <th
                    key={`headCell-${index}`}
                    style={style}
                >
                    {column.header}
                </th>
            );
        });

        return (
            <thead>
            <tr>{headers}</tr>
            </thead>
        );
    };

    export default TableHeader;

```
The body component, called TableRow.tsx, is below:

```
    import React from "react";
    import {ColumnDefinitionType} from "./Table";

    type TableRowsProps<T, K extends keyof T> = {
        data: Array<T>;
        columns: Array<ColumnDefinitionType<T, K>>;
    }

    const style = {
        border: '1px solid black'
    }

    const TableRows = <T, K extends keyof T>({ data, columns }: TableRowsProps<T, K>): JSX.Element => {
        const rows = data.map((row, index) => {
            return (
                <tr key={`row-${index}`}>
                    {columns.map((column, index2) => {
                            return (
                                <td key={`cell-${index2}`} style={style}>
                                    {row[column.key]}
                                </td>
                            );
                        }
                    )}
                </tr>
            );
        });

        return (
            <tbody>
            {rows}
            </tbody>
        );
    };

    export default TableRows;

```
	
For completeness, I am including the code for invoking the Table component:

```
    import React from 'react';
    import logo from './logo.svg';
    import './App.css';
    import Table from './Table';

    interface Cat {
      name: string;
      age: number;
      gender: string;
      color: string;
      activityLevel?: string; // optional, same as string | undefined
      favoriteFood?: string; // optional, same as string | undefined
    }

    const data: Cat[] = [
      {
        name: "Mittens",
        color: "black",
        age: 2,
        gender: "female",
        activityLevel: "hight",
        favoriteFood: "milk",
      },
      {
        name: "Mons",
        color: "grey",
        age: 2,
        gender: "male",
        favoriteFood: "old socks",
        activityLevel: "medium",
      },
      {
        name: "Luna",
        color: "black",
        age: 2,
        gender: "female",
        activityLevel: "medium",
        favoriteFood: "fish",
      },
      {
        name: "Bella",
        color: "grey",
        age: 1,
        gender: "female",
        activityLevel: "high",
        favoriteFood: "mice",
      },
      {
        name: "Oliver",
        color: "orange",
        age: 1,
        gender: "male",
        activityLevel: "low",
        favoriteFood: "fish",
      },
    ];

    const columns: ColumnDefinitionType<Cat, keyof Cat>[] = [
      {
        key: 'name',
        header: 'Name',
        width: 150
      },
      {
        key: 'age',
        header: 'Age in years',
      },
      {
        key: 'color',
        header: 'Color'
      }
    ]

    function App() {
      return (
        <div className="App">
          <Table data={data} columns={columns} />
        </div>
      );
    }

    export default App;

```
When I attempt to compile this code, I am seeing one problem in the TableRow component. I get the following compilation error:

```
    ERROR in src/TableRows.tsx:20:33
    TS2322: Type 'T[K]' is not assignable to type 'ReactNode'.
      Type 'T[keyof T]' is not assignable to type 'ReactNode'.
        Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'ReactNode'.
          Type 'T[string]' is not assignable to type 'ReactNode'.
            Type 'T[string]' is not assignable to type 'ReactPortal'.
              Type 'T[keyof T]' is not assignable to type 'ReactPortal'.
                Type 'T[K]' is not assignable to type 'ReactPortal'.
                  Type 'T[keyof T]' is not assignable to type 'ReactPortal'.
                    Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'ReactPortal'.
                      Type 'T[string]' is not assignable to type 'ReactPortal'.
        18 |                         return (
        19 |                             <td key={`cell-${index2}`} style={style}>
      > 20 |                                 {row[column.key]}
           |                                 ^^^^^^^^^^^^^^^^^
        21 |                             </td>
        22 |                         );
        23 |                     }

```
I have looked at documentation for React and Typescript, and I cannot see a reason for that line of code to fail to compile. Perhaps my understanding of string- based indexes is flawed?

Can someone give some idea of why this line of code is failing, and how I can get this TableRows component to work?