Exploring TypeScript Generics

Exploring TypeScript Generics

3 Min Read

Generics are a powerful feature of TypeScript. While there’s extensive content available on this topic, this post aims to explore generics more deeply with a niche use case, focusing on advanced applications, conditional types, and other interesting aspects.

This isn’t a typical introduction to generics. Instead, we’ll implement a unique scenario, using it to cover advanced generics, conditional types, and other features.

A Quick Refresher on Generics & Conditional Types

Let’s quickly cover the key concepts of this post with simplified examples.

If you’re already familiar, feel free to skip ahead. If not, this section might serve as a useful refresher.

Generics

Think of generics like function parameters that are types, normally dealing with values (or value references).

“`typescript
function arrayLength(arr: any[]) {
return arr.length;
}
“`

Here, `arr` is an array of `any`. We can type it more accurately using generics.

“`typescript
function arrayLengthTyped(arr: T[]) {
return arr.length;
}
“`

In this case, `T` infers the type of the array’s elements, though the original method was sufficient for counting elements.

Now let’s move to filtering.

“`typescript
function filterUntyped(array: any[], predicate: (item: any) => boolean): any[] {
return array.filter(predicate);
}
“`

This function lacks type checking on the predicate.

“`typescript
type User = { name: string; };

const users: User[] = [];

filterUntyped(users, user => user.nameX === “John”);
“`

Here, generics can ensure type checking.

“`typescript
function filterTyped(array: T[], predicate: (item: T) => boolean): T[] {
return array.filter(predicate);
}
“`

TypeScript will now catch potential errors.

“`typescript
filterTyped(users, user => user.nameX === “John”);
// —————————–^^^^^
// Property ‘nameX’ does not exist on type ‘User’. Did you mean ‘name’?
“`

Generics can limit arguments, useful for multiple user types.

“`typescript
type AdminUser = User & { role: string; };
type BannedUser = User & { reason: string; };
“`
To filter users correctly:

“`typescript
function filterUserCorrect(array: T[], predicate: (item: T) => boolean): T[] {
return array.filter(predicate);
}
“`

Conditional Types

These allow for creating types based on type evaluations.

“`typescript
type IsArray = T extends any[] ? true : false;
type YesIsArray = IsArray;
type NoIsNotArray = IsArray;
“`

`YesIsArray` is `true`; `NoIsNotArray` is `false`, illustrating the power of conditional types.

“`typescript
type ArrayOf = T extends Array ? U : never;

type NumberType = ArrayOf;
type NeverType = ArrayOf;
“`

Generic constraints work with helper types.

“`typescript
type ArrayOf2<T extends Array> = T extends Array ? U : never;

type NumberType2 = ArrayOf2;
type NeverType2 = ArrayOf2;
“`
Ensures proper types.

Let’s Get Started

In my two-part series on single flight mutations using TanStack, we created a helper for react-query options that worked seamlessly with server functions, keeping query functions and meta options in sync. Here’s a simplified example.

“`typescript
export function refetchedQueryOptions(queryKey: QueryKey, serverFn: any, arg?: any) {
const queryKeyToUse = […queryKey];
if (arg != null) {
queryKeyToUse.push(arg);
}
return queryOptions({
queryKey: queryKeyToUse,
queryFn: async () => { return serverFn({ data: arg }); },
meta: {
__revalidate: { serverFn, arg },
},
});
}
“`

We aimed to create a fully typed version of `refetchedQueryOptions`. It was challenging yet rewarding.

Our Success Criteria

Here’s our test setup with a partially working setup for refetchedQueryOptions, validating proper type checking.

“`typescript
import { QueryKey, queryOptions } from “@tanstack/react-query”;
import { createServerFn } from “@tanstack/react-start”;

export function refetchedQueryOptions(queryKey: QueryKey, serverFn: any, arg?: any) {
const queryKeyToUse = […queryKey];
if (arg != null) {
queryKeyToUse.push(arg);
}
return queryOptions({
queryKey: queryKeyToUse,
queryFn: async () => { return serverFn({ data: arg }); },
meta:

You might also like