dmytro-ulianov / batcave

code that I might wanna reuse

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

batcave

TypeScript

Utils

StrictUnion

this type helps to access members of all union constituents

reference: Twitter

here's a code to copy/paste

export type StrictUnion<T> = StrictUnionHelper<T, T>;

type UnionKeys<T> = T extends T ? keyof T : never;
type UnionDiff<T, TAll> = Exclude<UnionKeys<TAll>, keyof T>;
type StrictUnionHelper<T, TAll> = T extends any
  ? T & Partial<Record<UnionDiff<T, TAll>, undefined>>
  : never;

what and why? (scroll to the bottom to see the final example)

type Success<T> = { kind: "success"; value: T };
type Fail<E = unknown> = { kind: "fail"; error: E };

type Result<T, E = unknown> = Success<T> | Fail<E>;

function handleResult(result: Result<string>) {
  // we won't be able to descructure all values like that
  // property 'value' does not exist on type 'Result<string>'
  const { kind, value, error } = result;

  // to access value w/o an error we need to ensure that result is Success
  if (result.kind === "success") {
    const { value } = result;
  } else {
    const { error } = result;
  }

  // but sometimes we are ok with using value that might be null
  InfoCard({ value: result.value });
  function InfoCard(props: { value?: string | undefined }) {}
}

// to fix that we can modify our types with optional error and value
type Success<T> = { kind: "success"; value: T; error?: undefined };
type Fail<E = unknown> = { kind: "fail"; error: E; value?: undefined };

type Result<T, E = unknown> = Success<T> | Fail<E>;

function handleResult(result: Result<string>) {
  // now this works
  const { kind, value, error } = result;

  // and we can pass value that might be string or undefined
  InfoCard({ value: result.value });
  function InfoCard(props: { value?: string | undefined }) {}
}

// but now we have to manually adjust all types that have these optional fields

// or we can use a little type helper for that
type StrictUnion<T> = StrictUnionHelper<T, T>;
type UnionKeys<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> = T extends any
  ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, undefined>>
  : never;

// now what we need to do is to wrap our initial Result with StrictUnion
type Success<T> = { kind: "success"; value: T };
type Fail<E = unknown> = { kind: "fail"; error: E };
type Result<T, E = unknown> = StrictUnion<Success<T> | Fail<E>>;

// it works!
function handleResult(result: Result<string>) {
  const { kind, value, error } = result;
}

About

code that I might wanna reuse