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.