Amblem
Furkan Baytekin

Understanding Generic Types in TypeScript

A deep dive with practical examples

Understanding Generic Types in TypeScript
124
5 minutes

TypeScript is a powerhouse when it comes to building scalable, maintainable applications. One of its standout features is the ability to define generic types, a tool that provides flexibility and reusability across various scenarios. In this blog, we’ll explore the concept of generics using some practical examples that illustrate their power and utility.


What Are Generics?

Generics allow you to define components, functions, or types that can work with a variety of data types while maintaining type safety. Instead of hardcoding a specific type, generics let you use placeholders (like T, Data, or Props) to represent types, which are then replaced with actual types at runtime.

In essence, generics provide type parameters that make your code adaptable and reusable.


Example 1: Creating a Paginated Type

Pagination is a common feature in applications. A generic type can help define a consistent shape for paginated responses, regardless of the data type being paginated. Here’s how you can use generics for this purpose:

typescript
type Paginated<Data> = { data: Data[] page: number take: number total: number }

Breaking It Down:

Example Usage:

Imagine you’re paginating a list of users and posts. With the Paginated type, you can handle both scenarios without duplicating code:

typescript
type User = { id: number name: string } type Post = { id: number title: string content: string } // Paginated users const paginatedUsers: Paginated<User> = { data: [ { id: 1, name: "Alice" }, { id: 2, name: "Bob" } ], page: 1, take: 10, total: 50 } // Paginated posts const paginatedPosts: Paginated<Post> = { data: [ { id: 1, title: "Intro to TypeScript", content: "TypeScript is awesome!" }, { id: 2, title: "Generics 101", content: "Learn how to use generics." } ], page: 2, take: 5, total: 20 }

By using generics, the Paginated type adapts seamlessly to different data types, maintaining strict type safety.


Example 2: A Parent Component Type for React

In React, many components accept children as props. You can define a reusable type that ensures type safety for both the component’s props and its children:

typescript
type ParentComponent<Props = object> = Props & { children: React.ReactNode }

Breaking It Down:

Example Usage:

Here’s how you can use ParentComponent in a real-world scenario:

typescript
type CardProps = { title: string } const Card: React.FC<ParentComponent<CardProps>> = ({ title, children }) => <div className="card"> <h2>{title}</h2> <div>{children}</div> </div> // Using the Card component <Card title="Welcome"> <p>This is a reusable card component with generics.</p> </Card>

This type ensures that:

  1. The title prop is required for the Card component.
  2. The children prop is always valid and type-safe.

Example 3: Next.js Page Component with Context

Neneric types can make page components more robust by defining the structure of context and search parameters. Here’s a generic type to manage them:

typescript
type PageComponent<Context = object> = { params: Promise<Context> searchParams: Promise<Record<string, string | undefined>> }

Promises are required after version 15 in Next.js

Breaking It Down:

Example Usage:

Suppose you’re building a page that displays a blog post list based on its category slug. You can define a page component type like this:

typescript
type CategoryContext = { category: string } // For the URL /blogs/software?sort=desc&filter=popular const CategoryPage: React.FC<PageComponent<CategoryContext>> = async ({ params, searchParams }) => { const resolvedParams = await params const resolvedSearchParams = await searchParams console.log(resolvedParams) // { category: "typescript-generics" } console.log(resolvedSearchParams) // { sort: "desc", filter: "popular" } return <div> <h1>Slug: {resolvedParams.slug}</h1> <p>Sort: {resolvedSearchParams.sort}</p> </div> }

This ensures type safety when accessing the params and searchParams, reducing runtime errors and improving code readability.


Why Use Generics?


Conclusion

Generics are a fundamental part of TypeScript that make your code more reusable, flexible, and maintainable. Whether you’re handling paginated data, building reusable React components, or managing complex contexts in Next.js, generics can simplify and strengthen your codebase.

The examples above highlight the versatility of generics in TypeScript. By embracing this feature, you can write cleaner, safer, and more adaptable code that scales with your application’s needs.

Suggested Blog Posts