How to Create Generic Functional Components in React (Typescript)
Jasser Mark Arioste
Hello, hustler! In this tutorial, you’ll learn how to create generic functional components in react.
Why Create Generic React Components?
By creating generic components, we maximize the reusability of our code so that it caters to different types while still retaining its core functionality.
What do I mean by this?
Suppose that on one page of your app, you have a <UserList /> component that can sort by age or name . On another page, you have an <OrderList/> component that can sort by amount or createdDate . Both lists have the same sort function, but they are defined as two separate components since the properties of users are not the same as orders .
Without using generics, it’s impossible to create a generic list component with a sort functionality.
In this tutorial, we’ll create a generic list component to create one component for the two lists.
Defining Generic Functions in Typescript
First, let’s examine how to define generic functions in Typescript. We’ll only tackle the basics of generics here.
Since functional components in react are pretty much the same as functions in Typescript.
Defining a generic named function in Typescript
To define a generic named function, we need to add a generic type parameter <T> after the function name.
/**
* Transforms any value into an array of that value type
*/
export function toArray<T>(value: T): T[] {
return [value];
}
toArray("hello") //returns a string[]
toArray(5); //returns a number[]
type Person = {
name: string;
};
const person: Person = {name: "John Doe"}
toArray(person) //returns a Person[]
The <T> can be any descriptive name that fits your needs for example:
export function toArray<TValue>(value: TValue): TValue[] {
return [value];
}
If you’re expecting a certain type or interface , you can use the extends keyword. Let’s say you’re expecting an object with a fullName property:
interface WithFullName{
fullName: string
}
export function toArray<TValue extends WithFullName>(value: TValue): TValue[] {
return [value];
}
toArray({fullName: "JohnDoe"}) // good. no type error
toArray("test") //type error. The argument of type 'string' is not assignable to parameter of type 'WithFullName'
Defining a generic arrow function in Typescript
To define a generic arrow function in typescript, we add a generic parameter before the left parenthesis:
export const toArray = <TValue>(value: TValue): TValue[] => {
return [value];
};
//usage is the same as above
toArray(5) // returns [5]
toArray("hello") // returns ["hello"]
Defining Generic Components in React
Since we now know how to define generic functions in typescript, let’s define some generic react components. It’s pretty similar to typescript.
Defining a generic named Function Component
// components/ComponentA.tsx
type Props<T> = {
value: T;
};
export default function ComponentA<T>(props: Props<T>) {
return <pre>{JSON.stringify(props.value, null, 4)}</pre>;
}
Here we define a simple generic functional component that accepts any type of value and renders it as a string. Notice that it’s the same as the typescript function definition in the first example.
Defining a Generic Arrow Function Component
In .tsx files, there are two ways to define the generic arrow function component. To avoid ambiguity between type parameters and JSX.Element , there’s somewhat of an extra step when defining generic components.
- ) Using a trailing comma:
// components/TrailingComma.tsx
type Props<T> = {
value: T;
};
// notice the trailing comma after <T
const TrailingComma = <T,>(props: Props<T>) => {
return <pre>{JSON.stringify(props.value, null, 4)}</pre>;
};
export default TrailingComma;
Here we use a trailing comma in <T,> to clarify that it is a type parameter and not a JSX.Element . If we don’t use the trailing comma, it will confuse the compiler and will result in a type error:
- Using the
extendskeyword
If you don’t prefer the syntax of trailing commas, we can use the extends keyword which has the same purpose:
// components/ExtendsUknown.tsx
type Props<T> = {
value: T;
};
const ExtendsUnknown = <T extends unknown>(props: Props<T>) => {
return <pre>{JSON.stringify(props.value, null, 4)}</pre>;
};
export default ExtendsUnknown;
We use <T extends unknown> to clarify that this is a type parameter and not a JSX.Element . We use unknown since we’re not really expecting a specific type for T.
Example Component
All right, you now know how to define generic functional components in React, but here let me know you a concrete example. Suppose you need to create a generic <SortedList/> component that accepts items props. Each item can have any field. It also accepts a sortBy prop that is restricted to the actual properties of each item.
First, create the file components/SortedList.tsx , and copy the following code:
// components/SortedList.tsx
type Props<T extends Object> = {
items: T[];
sortBy: keyof T; //restricts the sortBy prop to the properties of T
sortOrder: 1 | -1;
renderItem(item: T, index: number): ReactNode;
};
export default function SortedList<T extends Object>({
items,
sortBy,
sortOrder,
renderItem,
}: Props<T>) {
// render the items
return <>SortedList</>;
}
Above, we’re just defining the skeleton of the component. Next, let’s render the items by using the renderItem function.
// components/SortedList.tsx
...
export default function SortedList<T extends Object>({
items,
sortBy,
sortOrder,
renderItem,
}: Props<T>) {
// sort the items
const sortedItems = [...items].sort();
// render the items
return <>{sortedItems.map(renderItem)}</>;
}
Next, let’s add actual sorting implementation:
// components/SortedList.tsx
...
export default function SortedList<T extends Object>({
items,
sortBy,
sortOrder,
renderItem,
}: Props<T>) {
// sort the items
const sortedItems = [...items].sort((a, b) => {
const primitiveType = typeof a[sortBy];
let result = 0;
switch (primitiveType) {
case "bigint":
case "number":
result = (a[sortBy] as number) - (b[sortBy] as number);
break;
case "string":
result = (a[sortBy] as string).localeCompare(b[sortBy] as string);
break;
default:
break;
}
return result * sortOrder;
});
// render the items
return <>{sortedItems.map(renderItem)}</>;
}
That’s it! To use this component, we just have to supply it with the correct props. Here’s an example of rendering a sorted list of users:
// pages/users.tsx
import SortedList from "../components/SortedList";
const users = () => {
const data = [
{
name: "John",
age: 16,
},
{
name: "Lebron",
age: 38,
},
{
name: "Jordan",
age: 23,
},
{
name: "Zach",
age: 27,
},
];
return (
<div>
<SortedList
items={data}
renderItem={(item, index) => {
return (
<div key={index}>
{item.name} - {item.age}
</div>
);
}}
sortBy="age"
sortOrder={1}
/>
</div>
);
};
export default users;
Notice that renderItem and sortBy is fully-typed and that it accepts only the properties of the data.
Full Code
If you want to check the full code, it can be accessed at GitHub: React Generic Components Tutorial
Conclusion
We learned how to create generic components in react and we also learned the different ways to define generic components in arrow functions. We applied what we learned by creating a generic SortedList component. For the next steps, try to see how you can apply generics in your web application.
Resources
Credits: Image by Samina Kousar from Pixabay


