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:
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:
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:
You can also be interested to read this article:
How to create React UIKIT components in TypeScript that extends native HTML Elements
Top comments (9)
yes, there is often an alternative way to to things.
That's just a simple example I did for a course a couple of days ago and I have shared it :)
offtopic: I'm removing all React.FC and VFC (deprecated) from my components and I'm using again to fn(props: MyType) and for children fn(props: PropsWithChildren).
It helped me a lot today. Thanks for the great blog!
You simplified something I was trying to understand for a while and I can't thank you enough. Thank you for putting this out!
not simple and stupid enough
do you mean the example should be simpler?
No , the whole Generic Component idea is not simple and stupid enough.
there are some good use cases and I have used it a couple of times but usually is not the best choice
ah ok.. Sure. It's just an example of use case.
I agree and I don't know use it a lot too : )
I cannot setup my react project with vite. Can you please help me
This is incredible. I just learnt about the
<GenericComponent<Type> />
syntax.Awesome stuff!!