A few months ago, while working on a React dashboard project for a client, I realized we were repeating the same component logic for different data types. Each component looked nearly identical; only the data structure varied. That’s when I decided to use TypeScript generics to make our components reusable, type-safe, and easy to maintain.
If you’ve ever found yourself duplicating React components just because of small data differences, this tutorial will show you the right way to handle it. I’ll walk you through how to create a React component with a generic type, explain how it works, and show a few practical examples you can use in your own projects.
What Are Generics in TypeScript?
Before we jump into code, let’s quickly understand what generics are.
Generics allow you to write flexible and reusable code that works with multiple data types, without losing type safety. They act like placeholders for types. You can think of them as “type variables” that you can assign later.
For example, instead of creating separate components for users, products, or orders, you can create a single generic component that adapts to any data type you pass in.
Why Use Generics in React Components?
Here’s why I rely on generics in React projects:
- Reusability: One component can handle multiple data types.
- Type Safety: TypeScript catches potential type mismatches at compile time.
- Cleaner Code: Less duplication, fewer bugs, and easier maintenance.
When building dashboards or admin panels, which are common in U.S. enterprise apps, generics can save hours of development time.
Method 1 – Create a Simple Generic Component
Let’s start with a basic example. Suppose we want to display a list of items, sometimes users, sometimes products. Instead of creating two separate components, we’ll make one generic component.
Here’s how I do it:
import React from 'react';
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{renderItem(item)}</li>
))}
</ul>
);
}
export default List;- The <T> after List declares a generic type parameter.
- The ListProps<T> interface ensures that the component works with any array type.
- The renderItem function defines how each item should be rendered.
Usage Example:
import React from 'react';
import List from './List';
const users = [
{ id: 1, name: 'John Doe', state: 'California' },
{ id: 2, name: 'Jane Smith', state: 'Texas' },
];
function App() {
return (
<div>
<h2>User List</h2>
<List
items={users}
renderItem={(user) => (
<span>
{user.name} ({user.state})
</span>
)}
/>
</div>
);
}
export default App;You can refer to the screenshot below to see the output.

When I first implemented this in a project, it felt like a breath of fresh air. I could reuse the same component for different lists without rewriting logic.
Method 2 – Generic Components with Constraints
Sometimes, you may want to restrict what types can be passed to your generic component. For instance, you might only want objects that contain an id property.
Here’s how I handle that:
import React from 'react';
interface Identifiable {
id: number;
}
interface DataListProps<T extends Identifiable> {
data: T[];
renderRow: (item: T) => React.ReactNode;
}
function DataList<T extends Identifiable>({ data, renderRow }: DataListProps<T>) {
return (
<div>
{data.map((item) => (
<div key={item.id}>{renderRow(item)}</div>
))}
</div>
);
}
export default DataList;You can refer to the screenshot below to see the output.

- The T extends the Identifiable constraint, ensuring that
Talways includes an id field. - This makes the component safer because key={item.id} will never fail.
Usage Example:
import React from 'react';
import DataList from './DataList';
const products = [
{ id: 101, name: 'MacBook Pro', price: 2499 },
{ id: 102, name: 'iPhone 15', price: 999 },
];
function ProductApp() {
return (
<div>
<h2>Product List</h2>
<DataList
data={products}
renderRow={(product) => (
<div>
{product.name} - ${product.price}
</div>
)}
/>
</div>
);
}
export default ProductApp;When working on e-commerce dashboards, this approach helps me ensure every product or order has a unique ID, preventing React key errors and improving performance.
Method 3 – Use Generics with Hooks and Custom Logic
Generics aren’t limited to components; you can use them in hooks too. Let’s create a custom hook that fetches data of any type.
import { useEffect, useState } from 'react';
function useFetch<T>(url: string) {
const [data, setData] = useState<T[] | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then((res) => res.json())
.then((json) => {
setData(json);
setLoading(false);
});
}, [url]);
return { data, loading };
}
export default useFetch;You can refer to the screenshot below to see the output.

- The hook is generic; it can fetch any type of data (users, products, etc.).
- The <T> ensures type safety when using the returned data.
Usage Example:
import React from 'react';
import useFetch from './useFetch';
import List from './List';
interface User {
id: number;
name: string;
email: string;
}
function UserList() {
const { data: users, loading } = useFetch<User>('https://jsonplaceholder.typicode.com/users');
if (loading) return <p>Loading...</p>;
return (
<div>
<h2>Users</h2>
{users && (
<List
items={users}
renderItem={(user) => (
<span>
{user.name} ({user.email})
</span>
)}
/>
)}
</div>
);
}
export default UserList;This method is incredibly useful for projects that consume multiple APIs. I’ve used it in analytics dashboards where different datasets share similar structures.
Method 4 – Combine Generics with Context API
Here’s a slightly advanced but practical example — using generics with React Context.
import React, { createContext, useContext, useState, ReactNode } from 'react';
interface ContextProps<T> {
data: T[];
setData: React.Dispatch<React.SetStateAction<T[]>>;
}
function createGenericContext<T>() {
const Context = createContext<ContextProps<T> | undefined>(undefined);
const useGenericContext = () => {
const context = useContext(Context);
if (!context) throw new Error('useGenericContext must be used within a Provider');
return context;
};
const Provider = ({ children }: { children: ReactNode }) => {
const [data, setData] = useState<T[]>([]);
return <Context.Provider value={{ data, setData }}>{children}</Context.Provider>;
};
return [Provider, useGenericContext] as const;
}
export default createGenericContext;- This function creates a reusable context for any data type.
- You can use it to manage global state for users, products, or even notifications.
Usage Example:
import React from 'react';
import createGenericContext from './createGenericContext';
interface Notification {
id: number;
message: string;
}
const [NotificationProvider, useNotification] = createGenericContext<Notification>();
function NotificationApp() {
const { data, setData } = useNotification();
return (
<div>
<h3>Notifications</h3>
<button
onClick={() =>
setData([...data, { id: data.length + 1, message: 'New alert received!' }])
}
>
Add Notification
</button>
<ul>
{data.map((note) => (
<li key={note.id}>{note.message}</li>
))}
</ul>
</div>
);
}
export default function App() {
return (
<NotificationProvider>
<NotificationApp />
</NotificationProvider>
);
}This approach is perfect for managing app-wide data with type safety, something I often use in large-scale React projects for U.S. clients.
Using TypeScript generics in React components has completely changed the way I build applications. It helps me write cleaner, safer, and more reusable code, especially when dealing with complex data structures.
Whether you’re building a small internal tool or a large enterprise dashboard, generics will make your components more robust and future-proof. Once you start using them, you’ll wonder how you ever managed without them.
You may also read:
- How to Use React Frame Component
- Get Fetch Results in a React Functional Component
- How to Use Card Component in React JS?
- Build a React Text Editor Component

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.