May 10, 2025
O. Wolfson
Generics in TypeScript are a way to make components, functions, or types more reusable and type-safe. They allow you to defer specifying a type until later, so your code can adapt to different shapes while still maintaining strict typing.
Define a generic identity function using <T>.
Accept a value of type T and return the same value.
Demonstrate with:
ts// identity.ts
function identity<T>(value: T): T {
return value;
}
// Example usage
const num = identity<number>(42); // num is type number
const str = identity<string>("hello"); // str is type string
const obj = identity<{ x: number }>({ x: 1 }); // obj is type { x: number }
Generics are useful when:
Without generics, you'd either have to:
any and lose type safetyLet's create a reusable <TypedInput /> component that accepts a label, a value, and a validation function โ and uses generics to remain flexible about what type the input holds.
It will show a validation error using ShadCN's Alert component when validation fails.
TypedInput.tsxtsx// components/TypedInput.tsx
"use client";
import { useState } from "react";
import { Input } from "@/components/ui/input";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { ExclamationTriangleIcon } from "@radix-ui/react-icons";
import { Label } from "@/components/ui/label";
type TypedInputProps<T> = {
label: string;
initialValue: T;
validate: (value: T) => string | null; // returns an error message or null
onChange: (value: T) => void;
};
export function TypedInput<T>({
label,
initialValue,
validate,
onChange,
}: TypedInputProps<T>) {
const [value, setValue] = useState<T>(initialValue);
const [error, setError] = useState<string | null>(null);
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
let raw = e.target.value;
let typedValue: any = raw;
if (typeof initialValue === "number") {
typedValue = Number(raw);
}
const error = validate(typedValue);
setValue(typedValue);
setError(error);
if (!error) onChange(typedValue);
}
return (
<div className="space-y-2">
<Label>{label}</Label>
<Input
value={value as any}
onChange={handleChange}
type={typeof initialValue === "number" ? "number" : "text"}
/>
{error && (
<Alert variant="destructive">
<ExclamationTriangleIcon className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
</div>
);
}
tsx"use client";
import { TypedInput } from "@/components/TypedInput";
export default function Page() {
return (
<div className="max-w-md mx-auto mt-10 space-y-8">
<TypedInput<number>
label="Age"
initialValue={0}
validate={(v) => (v < 18 ? "Must be at least 18." : null)}
onChange={(v) => console.log("โ
Valid age:", v)}
/>
<TypedInput<string>
label="Username"
initialValue=""
validate={(v) =>
v.length < 2 ? "Username must be at least 2 characters." : null
}
onChange={(v) => console.log("โ
Valid username:", v)}
/>
</div>
);
}
TypedInput<T> is generic over Tnumber and string, but the logic is reusedany, no type duplication, just safe reuse| Without Generics | With Generics |
|---|---|
Multiple components (NumberInput, TextInput) | One TypedInput<T> |
| Repeated validation logic | Reusable validation pattern |
any everywhere | Type-safe, inferred values |
Let's break down how generics power TypedInput<T>:
tsxtype TypedInputProps<T> = { ... }
T is a type parameter โ it stands in for whatever type you specify when you use the component (number, string, etc.).TypedInputProps<T> uses T to type the props: initialValue, validate(), and onChange(). This ensures all values stay consistent.tsxexport function TypedInput<T>({ ... }: TypedInputProps<T>) { ... }
T flows through the props and logic.handleChange, it infers how to coerce raw into a T using typeof initialValue.tsx<TypedInput<number> ... />
<TypedInput<string> ... />
T = number or T = string. This gives full autocomplete, validation, and inference โ no any, no duplication.๐ง Think of generics like templates for types โ they let you write code once, and fill in the types later.
Generics make your components reusable, predictable, and scalable. When you need flexibility without sacrificing type safety, generics are the way forward.