Home > Blockchain >  How to make generic list components from multiple types of list item
How to make generic list components from multiple types of list item

Time:09-08

I was making some Table components using Next.js, typescript, and type-graphql.
There are various custom object types of data, but eventually, they should be rendered into table rows.

Even though the detailed shape of the row would be different because the shapes of each data are different,
They are the same in that they should be rendered into a list of rows.

So I want to make the generic list component by the types of list data to reduce the writing of duplicated codes.

I'll roughly explain the code with the types of data as animals.
Currently inside of CatList.tsx:

const CatList = () => {
  const { list, total } = useQuery(/* data fetching */)
  // list is type of Cat[]
  
  return (
    <BaseTable>
      {/* this block is what I wanted to make into generic components */}
      {list.map((item: Cat) => (
        <CatListRow data={item} /*...other props*/ />
      )}
    </BaseTable>
  )
}

// same with dog in different file

const DogList = () => {
  const { list, total } = useQuery(/* data fetching */)
  // list is type of Dog[]
  
  return (
    <BaseTable>
      {list.map((item: Dog) => (
        <DogListRow data={item} /*...other props*/ />
      )}
    </BaseTable>
  )
}

I want to make each list like this:

const CatList = () => {
  const { list, total } = useQuery(/* data fetching */)
  
  return (
    <BaseTable>
      <Rows list={list} /* ...other props */ />
    </BaseTable>
  )
}

And I tried to make Rows.tsx like this:

type TableTarget = Cat | Dog | ... ;
/** each type has common fields
* __typename (string - ex: "Cat")
* id
* createdAt
* ...etc
*/

interface RowProps<T> {
  data: T;
}

const CatRow = ({ data }: RowProps<Cat>) => {
  /* Cat Row */
}

const DogRow = ({ data }: RowProps<Dog>) => {
  /* Dog Row */
}


const getRowComponentByType = (target: TableTarget) => {
  switch(target.__typename) {
    case 'Cat':
      return CatRow;
    case 'Dog':
      return DogRow;
    ...
  }
}


interface RowsProps<T extends TableTarget> {
  list: T[];
}

const Rows = <T>({ list }: RowsProps<T>) => {
  if (list.length === 0) {
    return (
      <tr>list is empty.</tr>
    )
  }
  
  const Row = getRowComponentByType(list[0])
  return (
    <>
      {list.map((item: T) => {
        <Row data={item} />
        {/* red underline on 'data' prop */}
        {/* error message: 'TableTarget' is not assignable to 'Cat & Dog' */}
      })}
    </>
  )
}

export default Rows;

I want to know how can i complete the Rows component with proper usage of typescript generics.

Thank you in advance.

CodePudding user response:

This is working for me, but if you need further help I think I'd need a stackblitz

type Cat = {};
type Dog = {};
type TableTarget = Cat | Dog;

interface RowsProps<T extends TableTarget> {
  list: T[];
}

function Rows<T extends TableTarget>({ list }: RowsProps<T>): any {
  list.map(() => {

  })
}

function row<T extends TableTarget>(props: RowsProps<T>): any{
  return {list: []}
}
  • Related