Home > Back-end >  React.js TypeScript (Generics) - Type 'T' is not assignable to type 'ReactNode'
React.js TypeScript (Generics) - Type 'T' is not assignable to type 'ReactNode'

Time:01-23

I have code a List component (List.tsx) to show items, like following:

type PersonInGeneric = {
  id: number;
  first: string;
  last: string;
};

type ListProps<T> = {
  items: T[];
  onClick: (value: T) => void
};

export const List = <T extends PersonInGeneric>({ 
  items, 
  onClick, 
}: ListProps<T>) => {
  return(
    <>
      <h2>List of items</h2>
      {items.map((item, index) => {
        return(
          <div key={item.id} onClick={() => onClick(item)}>
            {item}
          </div>
        );
      })}
    </>
  );
}

And this is how I implement it in App.tsx (no error here) :

<List
        items={[
          {
            id: 1,
            first: 'Kai',
            last: 'Doe',
          }
        ]}
        onClick={item => console.log(item)}
/>

However, there is an error in List.tsx and if I hover on {item} it says :

" Type 'T' is not assignable to type 'ReactNode'. Type 'PersonInGeneric' is not assignable to type 'ReactNode'. Type 'T' is not assignable to type 'ReactPortal'. Type 'PersonInGeneric' is missing the following properties from type 'ReactPortal': key, children, type, propsts "

What is wrong with List.tsx? Thanks in advance.

CodePudding user response:

The error message you're seeing is telling you that the type T passed to the List component is not assignable to the type ReactNode. This is because the map function in the JSX is expecting to return a valid React element, but the type of T is not guaranteed to be a valid React element.

One solution to this problem would be to ensure that the type T passed to the List component has a toString() method that returns a string representation of the item, which can then be rendered in the JSX.

You can change the return statement in the map function like this:

{ items.map((item, index) => {
    return(
      <div key={item.id} onClick={() => onClick(item)}>
        {item.toString()}
      </div>
    );
  })}

It would be required that T have a toString method, which would be the case in your example since T is of type PersonInGeneric.

Alternatively, you could create a separate component for each item and pass the item as a prop to that component, which would then be responsible for rendering the item. This approach would allow for more flexibility in terms of how each item is rendered and would not rely on the toString() method.

 const Item = ({ item }: { item: T }) => {
  return (
    <div onClick={() => onClick(item)}>
      {item.first} {item.last}
    </div>
  );
};

{items.map((item, index) => {
    return <Item key={item.id} item={item} />;
  })}

It would be a good idea to choose one of the above alternatives or a different approach that best fits your use case and requirements. Hope that helps :)

  • Related