'Infer child component generics from parent type

I would like to create a table component which utilizes components to define the columns like this:


interface UsageModel {
  title: string;
}

const Usage = () => {
  const data: UsageModel[] = [{ title: "Stack Overflow" }]
  return <Table data={data}>
    <TableField header="Title">{row => row.title}</TableField>
  </Table>
}

This works technically but Typescript doesn't know the type of row (resulting in unknown) which is kinda annoying because you have to explicitly type every field. I was expecting Typescript to get the generic type because of the typed children prop of the table but I guess this doesn't work because of how JSX is implemented?

Does anyone know of a way to teach Typescript how to do this?

EDIT: I've created a Playground with a working example here.

The table code (minus the implementation) looks like this:

import { Component, ReactElement, ReactNode } from "react"

export interface TableFieldProps<T> {
  children: ((row: T) => ReactNode) | ReactNode
  header?: ReactNode
}

export class TableField<T> extends Component<TableFieldProps<T>> {
}

export interface TableProps<T> {
  data: T[]
  children: TableFieldElement<T> | TableFieldElement<T>[]
}

type TableFieldElement<T> = ReactElement<TableFieldProps<T>, typeof TableField>

export function Table<T>({ data, children }: TableProps<T>) {
  return (<table/>)
}

The compiler options of typescript look like this:

{
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es2021",
    "module": "esnext",
    "jsx": "react-jsx",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source