iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

TS Examples

A grab-bag of small, real TypeScript patterns you can paste into the editor and adapt.

Typed fetch wrapper

TS
async function json<T>(input: RequestInfo, init?: RequestInit): Promise<T> {
    const res = await fetch(input, init);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json() as Promise<T>;
}

type User = { id: number; name: string };
const u = await json<User>('/api/users/1');

Result type (Rust-flavoured)

TS
type Result<T, E = string> =
    | { ok: true;  value: T }
    | { ok: false; error: E };

function parseId(s: string): Result<number> {
    const n = Number(s);
    return Number.isInteger(n)
        ? { ok: true,  value: n }
        : { ok: false, error: `not an int: ${s}` };
}

Tiny EventEmitter

TS
type Events = {
    login:  { user: string };
    logout: void;
    error:  { code: number; message: string };
};

class TypedEmitter<E> {
    private listeners = new Map<keyof E, Set<(arg: any) => void>>();

    on<K extends keyof E>(event: K, fn: (arg: E[K]) => void) {
        if (!this.listeners.has(event)) this.listeners.set(event, new Set());
        this.listeners.get(event)!.add(fn as any);
    }

    emit<K extends keyof E>(event: K, arg: E[K]) {
        this.listeners.get(event)?.forEach(fn => fn(arg));
    }
}

const bus = new TypedEmitter<Events>();
bus.on('login', e => console.log(e.user));    // typed!

Type-safe deep get

TS
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const u = { id: 1, name: 'Ada' };
const id = get(u, 'id');     // number
// const z = get(u, 'foo');  // ✗ error

Branded type for IDs

TS
type UserId = number & { readonly __brand: 'UserId' };
type PostId = number & { readonly __brand: 'PostId' };

function fetchUser(id: UserId) { /* ... */ }
const a = 1 as UserId;
fetchUser(a);

const b = 1;
// fetchUser(b);    // ✗ — plain number can't masquerade as UserId

Exhaustive switch

TS
type Shape =
    | { kind: 'circle'; r: number }
    | { kind: 'rect';   w: number; h: number };

function area(s: Shape): number {
    switch (s.kind) {
        case 'circle': return Math.PI * s.r ** 2;
        case 'rect':   return s.w * s.h;
        default: {
            const _: never = s;
            throw new Error('unhandled');
        }
    }
}
Tip: Whenever you find a pattern useful, save it as a snippet in your editor. A small personal library of TypeScript patterns is one of the highest-leverage things you'll build.

Example

Example
// A few patterns to paste into the editor:

// 1. typed reduce
const sum = [1, 2, 3].reduce<number>((c, n) => c + n, 0);

// 2. Discriminated union
type Result<T> = { ok: true; value: T } | { ok: false; error: string };

// 3. Generic constraint
function key<T extends object>(o: T): keyof T { return Object.keys(o)[0] as keyof T; }

console.log(sum, key({ a: 1, b: 2 }));
Try it Yourself »

Exercise

Pattern for "value tagged as a brand" — phantom property name typical convention.

type UserId = number & { readonly __: 'UserId' };

Test yourself

Q1. A typed "fetch" wrapper usually uses…
Q2. Branded types prevent…
Q3. Exhaustive switch uses…

Discussion

Loading…