Create a React / TypeScript Generic Component

Often we need to create generic components in React / TypeScript that need to accept any kind of type.

Since we want to create reusable components and, at the same time, they should be type-safed too, we cannot define its own props as any type, and un…


This content originally appeared on DEV Community and was authored by Fabio Biondi

Often we need to create generic components in React / TypeScript that need to accept any kind of type.

Since we want to create reusable components and, at the same time, they should be type-safed too, we cannot define its own props as any type, and unknown is not often a valid solution.

Now let's imagine if we have to create a TabBar component in React/TypeScript that accepts an items property of any type of array(string[], User[], Whatever[]):

<TabBar
  items={anyTypeOfArray}
  onTabClick={selectHandler}
/>

The output:

Demo

If the TabBar items property should accept any kind of type we may think to use any[]. Right? Ehm... no 😅
We completely lose type checking!

interface TabBarProps<T> {
  items: any[];
  selectedItem: any;
  onTabClick: (item: any, selectedIndex: number) => void
}

In fact, by using any, the TypeScript compiler and your IDE/editor are not able to know which type of parameters your onTabClick will come back or what type of data selectedItem should accepts:

Ide problem

Solution

Instead of using any we can pass a generic type to our component:

1) First, we create a custom type (in this example MySocial but it could be anything):

interface MySocial {
  id: number;
  name: string;
  link: string;
}

const socials: MySocial[] = [
  { id: 11, name: 'WebSite', link: 'https://www.fabiobiondi.dev'},
  { id: 12, name: 'Youtube', link: 'https://www.youtube.com/c/FabioBiondi'},
  { id: 13, name: 'Twitch', link: 'https://www.twitch.tv/fabio_biondi'},
]

2) We can pass this type to the component as generic:

<TabBar<MySocial>
  selectedItem={selectedSocial}
  items={socials}
  onTabClick={selectHandler}
/>

3) Our TabBar component should now use generics instead of any.
We can also decide this type must includes id and name in its definition:

interface TabBarProps<T> {
  items: T[];
  selectedItem: T;
  onTabClick: (item: T, selectedIndex: number) => void
}

export function TabBar<T extends { id: number, name: string}>(props: TabBarProps<T>) {

  // ... your component code here ...

Final Source Code

Here the complete source code of TabBar (it uses Tailwind for CSS but it doesn't matter) :

// TabBar.tsx
interface TabBarProps<T> {
  items: T[];
  selectedItem: T;
  onTabClick: (item: T, selectedIndex: number) => void
}

export function TabBar<T extends { id: number, name: string}>(props: TabBarProps<T>) {
  const { items, selectedItem, onTabClick} = props;
  return (
    <>
      <div className="flex gap-x-3">
        {
          items.map((item, index) => {
            const activeCls = item.id === selectedItem.id ? 'bg-slate-500 text-white' : ' bg-slate-200';
            return <div
                key={item.id}
                className={'py-2 px-4 rounded ' + activeCls}
                onClick={() => onTabClick(item, index)}
              >
                {item.name}
              </div>
            }
          )
        }
      </div>
    </>
  )
}

Usage

Following an example of usage:

// App.tsx
import { useState } from 'react';
import { TabBar } from '../../../shared/components/TabBar';

interface MySocial {
  id: number;
  name: string;
  link: string;
}

const socials: MySocial[] = [
  { id: 11, name: 'WebSite', link: 'fabiobiondi.dev'},
  { id: 12, name: 'Youtube', link: 'YT'},
  { id: 13, name: 'Twitch', link: 'twitch'},
]

export const App = () => {
  const [selectedSocial, setSelectedSocial] = useState<MySocial>(socials[0])

  function selectHandler(item: MySocial, selectedIndex: number) {
    setSelectedSocial(item)
  }

  return (
    <div>
      <h1>Tabbar Demo</h1>
        <TabBar<MySocial>
          selectedItem={selectedSocial}
          items={socials}
          onTabClick={selectHandler}
        />

      <div className="border border-slate-200 border-solid rounded my-3 p-5">
        <a href={selectedSocial.link}>Visit {selectedSocial.name}</a>
      </div>
    </div>
  )
};

Result:

Final Demo Animated


This content originally appeared on DEV Community and was authored by Fabio Biondi


Print Share Comment Cite Upload Translate Updates
APA

Fabio Biondi | Sciencx (2022-07-03T14:52:10+00:00) Create a React / TypeScript Generic Component. Retrieved from https://www.scien.cx/2022/07/03/create-a-react-typescript-generic-component/

MLA
" » Create a React / TypeScript Generic Component." Fabio Biondi | Sciencx - Sunday July 3, 2022, https://www.scien.cx/2022/07/03/create-a-react-typescript-generic-component/
HARVARD
Fabio Biondi | Sciencx Sunday July 3, 2022 » Create a React / TypeScript Generic Component., viewed ,<https://www.scien.cx/2022/07/03/create-a-react-typescript-generic-component/>
VANCOUVER
Fabio Biondi | Sciencx - » Create a React / TypeScript Generic Component. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2022/07/03/create-a-react-typescript-generic-component/
CHICAGO
" » Create a React / TypeScript Generic Component." Fabio Biondi | Sciencx - Accessed . https://www.scien.cx/2022/07/03/create-a-react-typescript-generic-component/
IEEE
" » Create a React / TypeScript Generic Component." Fabio Biondi | Sciencx [Online]. Available: https://www.scien.cx/2022/07/03/create-a-react-typescript-generic-component/. [Accessed: ]
rf:citation
» Create a React / TypeScript Generic Component | Fabio Biondi | Sciencx | https://www.scien.cx/2022/07/03/create-a-react-typescript-generic-component/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.