Skip to main content

Command Palette

Search for a command to run...

5) Typescript Union Types

Updated
  1. Union Types
    A Union Type allows a variable to hold multiple possible types. A union type is created using the | (pipe) symbol between the types.

    type Status = "loading" | "success" | "error";
    // OR
    let value: string | number;
    

    This means value can be either a string or a number.
    i) Union Types with Props

    type ButtonProps = {
      label: string;
      variant: "primary" | "secondary";
    };
    
    function Button({ label, variant }: ButtonProps) {
      return (
        <button className={variant}>
          {label}
        </button>
      );
    }
    
    <Button label="Save" variant="primary" />
    
    // If someone writes
    <Button label="Save" variant="danger" />
    // TypeScript throws an error because "danger" is not allowed.
    

    ii) Union Types with State

    const [status, setStatus] = useState<
      "idle" | "loading" | "success" | "error"
    >("idle");
    
  2. Narrowing
    TypeScript cannot automatically know which type is currently being used inside a union. Narrowing helps TypeScript determine the exact type.
    i) Example Without Narrowing

    function print(value: string | number) {
      console.log(value.length);
    }
    // Property 'length' does not exist on type 'number'
    // Because number doesn't have .length.
    

    ii) Narrowing Using typeof

    function print(value: string | number) {
      if (typeof value === "string") {
        console.log(value.length);
      } else {
        console.log(value.toFixed(2));
      }
    }
    

    Now TypeScript understands:
    inside if → value is string
    inside else → value is number
    iii) Narrowing in React

    type Props = {
      data: string | string[];
    };
    
    function Display({ data }: Props) {
      return (
        <div>
          {typeof data === "string"
            ? data
            : data.join(", ")}
        </div>
      );
    }
    
  3. Type Guards
    A Type Guard is logic that checks a type during runtime and helps TypeScript narrow the type.
    i) typeof - typeof value === "string"
    ii) instanceof - value instanceof Date
    iii) in operator - "name" in obj
    iv) Custom type guard - isUser(value)
    In Operator

    type Admin = {
      role: string;
    };
    
    type User = {
      email: string;
    };
    
    function printPerson(person: Admin | User) {
      if ("role" in person) {
        console.log(person.role);
      } else {
        console.log(person.email);
      }
    }
    

    Custom Type Guard

    function isString(value: unknown): value is string {
      return typeof value === "string";
    }
    
    function print(value: unknown) {
      if (isString(value)) {
        console.log(value.toUpperCase());
      }
    }
    
  4. Unknown Type
    unknown is a safer version of any

    // with any
    let value: any = "Hello";
    value.toUpperCase(); // allowed
    // No type safety.
    
    // with unknown
    let value: unknown = "Hello";
    value.toUpperCase(); // Error
    

    unknown vs any
    i) unknown - Type-safe
    any - No type checking
    ii) unknown - Safer
    any - Dangerous
    iii) unknown - Requires narrowing
    any - Allows everything

    async function fetchData(): Promise<unknown> {
      const response = await fetch("/api/users");
      return response.json();
    }
    
  5. never Type
    never means: This value should never happen.
    Used for: impossible states, exhaustive checks, functions that never return.
    React Example

    type State = {
      count: number;
    };
    
    type Action =
      | { type: "increment" }
      | { type: "decrement" };
    
    function reducer(
      state: State,
      action: Action
    ): State {
      switch (action.type) {
        case "increment":
          return { count: state.count + 1 };
    
        case "decrement":
          return { count: state.count - 1 };
    
        default:
          const exhaustive: never = action;
          return state;
      }
    }
    
  6. Concepts
    i) Union Types - Allow multiple types
    ii) Narrowing - Reduce union into specific type
    iii) Type Guards - Runtime checks for narrowing
    iv) unknown - Safer alternative to any
    v) never - Impossible values/exhaustive checks