267 words, 2 min read

Instead of writing this:

type DataType = {
type: "idle" | "loading" | "done" | "error" | "invalid";
};
const obj: DataType = {
type: "idle"
};
if (obj.type === "idle") {
// ...
} else if (obj.type === "loading") {
// ...
} else if (obj.type === "done") {
// ...
} else if (obj.type === "error") {
// ...
} else if (obj.type === "invalid") {
// ...
}

You can use the ts-pattern library to write it like this instead:

import { match } from "ts-pattern";
type Result = {
type: "idle" | "loading" | "done" | "error" | "invalid";
};
const result: Result = { type: "error" };
match(result)
.with({ type: "idle" }, () => console.log("idle"))
.with({ type: "error" }, () => console.log("error"))
.with({ type: "done" }, () => console.log("error"))
.exhaustive();

I personally find the latter a lot more readable, less verbose and easier to understand.

It even allows you to do things like this:

import { match, P } from 'ts-pattern';
type Data =
| { type: 'text'; content: string }
| { type: 'img'; src: string };
type Result =
| { type: 'ok'; data: Data }
| { type: 'error'; error: Error };
const result: Result = ...;
const html = match(result)
.with({ type: 'error' }, () => <p>Oups! An error occured</p>)
.with({ type: 'ok', data: { type: 'text' } }, (res) => <p>{res.data.content}</p>)
.with({ type: 'ok', data: { type: 'img', src: P.select() } }, (src) => <img src={src} />)
.exhaustive();

It's almost a nice as pattern matching in Elixir.

You can find more examples and the full explanation here.