We use cookies to enhance your experience on the site
CodeWorlds

Generic Components - Universal Navigation Systems

Imagine a universal navigation system on the spaceship that can display data about planets, stars, ships - whatever you feed it. In TypeScript, we create such "universal" components using generics.

What are Generics?

Generics are type parameters - they allow you to create components and functions that work with different data types while maintaining full type control:

1// Without generics - you need separate functions
2function getFirstPlanet(items: Planet[]): Planet {
3  return items[0];
4}
5function getFirstStar(items: Star[]): Star {
6  return items[0];
7}
8
9// With generics - one function for all types
10function getFirst<T>(items: T[]): T {
11  return items[0];
12}
13
14const planet = getFirst<Planet>(planets);  // returns Planet
15const star = getFirst<Star>(stars);        // returns Star
16const name = getFirst<string>(["a", "b"]); // returns string

Generic List Component

One of the most common uses of generics in React is a universal list component:

1// Each element must have an id for display
2interface HasId {
3  id: number | string;
4}
5
6interface ListProps<T extends HasId> {
7  items: T[];
8  renderItem: (item: T) => React.ReactNode;
9  emptyMessage?: string;
10}
11
12function List<T extends HasId>({ items, renderItem, emptyMessage = "No data" }: ListProps<T>) {
13  if (items.length === 0) {
14    return <p>{emptyMessage}</p>;
15  }
16
17  return (
18    <ul>
19      {items.map(item => (
20        <li key={item.id}>{renderItem(item)}</li>
21      ))}
22    </ul>
23  );
24}

Using the Generic Component

1interface Planet {
2  id: number;
3  name: string;
4  type: string;
5}
6
7interface CrewMember {
8  id: string;
9  name: string;
10  rank: string;
11}
12
13function Dashboard() {
14  const planets: Planet[] = [
15    { id: 1, name: "Mars", type: "rocky" },
16    { id: 2, name: "Jupiter", type: "gas giant" }
17  ];
18
19  const crew: CrewMember[] = [
20    { id: "c1", name: "Nova", rank: "Captain" },
21    { id: "c2", name: "Ra", rank: "Navigator" }
22  ];
23
24  return (
25    <div>
26      <h2>Planets</h2>
27      <List<Planet>
28        items={planets}
29        renderItem={(planet) => <span>{planet.name} ({planet.type})</span>}
30      />
31
32      <h2>Crew</h2>
33      <List<CrewMember>
34        items={crew}
35        renderItem={(member) => <span>{member.name} - {member.rank}</span>}
36      />
37    </div>
38  );
39}

Generic Select Component

1interface SelectOption {
2  value: string;
3  label: string;
4}
5
6interface SelectProps<T extends SelectOption> {
7  options: T[];
8  value: string;
9  onChange: (option: T) => void;
10  placeholder?: string;
11}
12
13function Select<T extends SelectOption>({
14  options, value, onChange, placeholder
15}: SelectProps<T>) {
16  const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
17    const selected = options.find(opt => opt.value === e.target.value);
18    if (selected) onChange(selected);
19  };
20
21  return (
22    <select value={value} onChange={handleChange}>
23      {placeholder && <option value="">{placeholder}</option>}
24      {options.map(opt => (
25        <option key={opt.value} value={opt.value}>
26          {opt.label}
27        </option>
28      ))}
29    </select>
30  );
31}

Generic Constraints

The

extends
keyword lets you restrict which types are accepted:

1// T must have a 'name' field
2function displayName<T extends { name: string }>(item: T): string {
3  return item.name;
4}
5
6// T must implement the Sortable interface
7interface Sortable {
8  sortOrder: number;
9}
10
11function sortItems<T extends Sortable>(items: T[]): T[] {
12  return [...items].sort((a, b) => a.sortOrder - b.sortOrder);
13}
Go to CodeWorlds