shackra
shackra

Reputation: 366

Dynamic generic for prop argument in React component

I made a custom table React component that helps me present several arrays of maps with different fields defined written in JavaScript, I'm moving my code base to Typescript to leverage types but I'm having issues with this component, I decided to use generics but I'm facing issues with that too in regard to tsc:

Argument of type 'string' is not assignable to parameter of type 'keyof T'. Type 'string' is not assignable to type '"idx" | "ID"'.

Here is a minimal example that reproduces what I see with the code of my component:

import React from 'react'

interface MyTableProps<T> {
    arr: Array<T>
    fields: Array<string>
}

const getProperty = <T, K extends keyof T>(obj: T, key: K) => {
    return obj[key]
}

const MyTable = <T extends { idx?: string | number; ID?: string | number }>({ arr, fields }: MyTableProps<T>) => {
    return (
        <table>
            <thead>
                <tr>
                    {fields.map(field => (<th key={field}>{field}</th>))}
                </tr>
            </thead>
            <tbody>
                {arr.map(row => (
                    <tr key={row.idx || row.ID}>
                        {
                            fields.map(field => (
                                <td key={"td"+field}>{getProperty(row, field)}</td>
                            ))
                        }
                    </tr>
                ))}
            </tbody>
        </table>)
}

The error quoted above comes from the getProperty function second argument.

My question is: What should I do to have a dynamic generic type for the prop argument in my React component?

Upvotes: 0

Views: 1431

Answers (2)

georgekrax
georgekrax

Reputation: 1189

It is recommended to use these patterns, from the React TypeScript Cheatsheet. You can find useful things about how to wrap components with generic HTML element props. In few words, it states that it is better to use the React.ComponentPropsWithoutRef or React.ComponentPropsWithRef (if you want to pass a ref to the component). Here is a part of their examples:

// usage
function App() {
// Type '"foo"' is not assignable to type '"button" | "submit" | "reset" |`undefined'.(2322)
// return <Button type="foo"> sldkj </Button>
   
// no error
    return <Button type="button"> text </Button>;
}
    
// implementation
export interface ButtonProps extends React.ComponentPropsWithoutRef<"button"> {
  specialProp?: string;
}
export function Button(props: ButtonProps) {
    const { specialProp, ...rest } = props;
    // do something with specialProp
    return <button {...rest} />;
}

Upvotes: 1

Lauren Yim
Lauren Yim

Reputation: 14078

Try using this for MyTableProps<T>:

interface MyTableProps<T> {
    arr: Array<T>
    fields: Array<keyof T>
}

Upvotes: 1

Related Questions