React prop type based on other prop's value

Viewed 35

I want to build a React Table component, here is the TableProps:

export interface ColumnType<ItemType, Key extends keyof ItemType = keyof ItemType> {
  header: string;
  key?: keyof ItemType;
  renderCell: (value: ItemType[Key] | undefined, item: ItemType, index: number) => React.ReactNode;
}
export interface TableProps<ItemType extends object> {
  data: ItemType[];
  columns: ColumnType<ItemType>[];
}

My goal is to make the argument type of renderCell can be generated base on the value of key, such as:

interface Person {
  firstName: string;
  age: number;
}

        <Table
          data={[
            {
              firstName: 'xxx',
              age: 111,
            },
          ]}
          columns={[
            {
              header: 'firstName',
              key: 'firstName',
              renderCell: (v) => v, // type of v should be string here
            },
          ]}
        />

1 Answers

One way of achieving this is removing the whole key prop in the ColumnType

A working example could be found at codesandbox

This would leave you with the following interfaces and Table component

import { ReactElement, ReactNode } from "react";

export interface ColumnType<ItemType> {
  header: string;
  renderCell: (item: ItemType, index: number) => ReactNode;
}
export interface TableProps<ItemType> {
  data: ItemType[];
  columns: ColumnType<ItemType>[];
}

export const Table = <ItemType extends object>(
  props: TableProps<ItemType>
): ReactElement => {
  return (
    <table>
      <thead>
        <tr>
          {props.columns.map((column) => (
            <th>{column.header}</th>
          ))}
        </tr>
      </thead>
      <tbody>
        <tr>
          {props.data.map((row, index) =>
            props.columns.map((column) => column.renderCell(row, index))
          )}
        </tr>
      </tbody>
    </table>
  );
};

which could be used as follows:

import "./styles.css";
import { Table } from "./Table";

interface Person {
  firstName: string;
  age: number;
}

export default function App() {
  return (
    <Table<Person>
      data={[{ firstName: "xiduzo", age: 999 }]}
      columns={[
        {
          header: "Name",
          renderCell: (item, index) => {
            return <td key={index}>{item.firstName}</td>;
          }
        },
        {
          header: "Age",
          renderCell: (item, index) => {
            return <td key={index}>{item.age}</td>;
          }
        }
      ]}
    />
  );
}
Related