Skip to content

Instantly share code, notes, and snippets.

@alexeden
Last active November 19, 2024 17:29
Show Gist options
  • Select an option

  • Save alexeden/853bdb3a4081762d698a377532802f04 to your computer and use it in GitHub Desktop.

Select an option

Save alexeden/853bdb3a4081762d698a377532802f04 to your computer and use it in GitHub Desktop.
Pattern 001: Leverage Typescript union types (part 1)

Pattern 001: Leverage Typescript union types (part 1)

Leverage what?

Union types. They enable cool things and can eliminate an entire class of bugs that would only otherwise be discovered at runtime.

They look like this:

type Key = string | number | symbol;

type Units = "${number}px" | "${number}%" | "none";

type Shape =
  | { name: "circle"; radius: number }
  | { name: "square"; side: number }
  | { name: "rectangle"; height: number; width: number };

The Typescript website's definition of a union type: A union type describes a value that can be one of several types. We use the vertical bar (|) to separate each type, so number | string | boolean is the type of a value that can be a number, a string, or a boolean.

If you're familiar with sum types in Haskell or enum types in Rust, you already know union types.

Use case: Move value-dependent/conditional type checking to compile-time

πŸ‘‹ The example I'm providing is of a React component and its Props type, but this pattern is not specific to React, nor the frontend!

❌ Don't do this

Values depend on the value of other values all the time. In the case of a component, you might see something like this in a PayIt frontend app:

type Props = {
  alt?: string; // This must be provided if `label` is undefined!
  label?: ReactChild;
}

export const Component = (props: Props) => {

The comment warns us alt must be defined if label is undefined (presumably to ensure accessibility), but there's nothing to stop me from using this component incorrectly. What's worse, I won't know about it until the code actually runs. (and, in practice, sometimes we're lucky just to have a type definition, much less one with a descriptive comment)

<Component /> // BAD: I should be providing an `alt` prop!

βœ… Do this

We can encode Props' intra-type dependency between alt on the value of label in the type itself.

type Props = 
  | { label: ReactChild } 
  | { alt: string; label?: undefined };

And now:

<Component /> // ❌ Type error: Property `alt` is missing... πŸŽ‰
<Component alt="alt hello" /> // βœ…
<Component label="Hello" /> // βœ…

Typescript's control flow analysis also applies the rules when we try to do something with a value of our type. e.g. given a value props of type Props, let's say we want to trim any white space off alt:

props.alt.trim(); // ❌ Type error: Property 'alt' does not exist on type '{ label: ReactChild }'

The compiler just saved us from a potential bug.

if (props.label) {
  props.alt.trim(); // πŸ‘
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment