Typescript

Function overload

Function overload has a serious drawback. It needs to have all possible combinations of arguments covered. With a couple of arguments the complexity quickly get out of hand.

function foo(arg1: number, arg2: number): number
function foo(arg1: string, arg2: string): string
function foo(arg1: string | number, arg2: string | number): string | number {
  return arg1 || arg2
}

// ❎ x is of type string
const x = foo('sample1', 'sample2')
// ❎ y is of type number
const y = foo(10, 24)

console.log(`x`, typeof x, x)
console.log(`y`, typeof y, y)

export default foo
Pick<Type, Keys>
interface Todo {
  title: string
  description: string
  completed: boolean
}

type TodoPreview = Pick<Todo, 'title', 'completed'>

const todo: TodoPreview = {
  title: 'Clean room',
  completed: false,
}
Non nullable type

Removes null or undefined from a type.

type NonNullable<T> = T extends null | undefined ? never : T
//usage
type MyType = string | null
const a: NonNullable<MyType> = 'myString' // Type is only string
Readonly and optional mapped types

Redefine a type.

interface MyReadOnlyType {
  readonly [P in K]: T
}
interface MyOptionalType {
  [P in K]?: T
}
Common mapped types
 { [ P in K ] : T }
 { [ P in K ] ?: T }
 { [ P in K ] -?: T }
 { readonly [ P in K ] : T }
 { readonly [ P in K ] ?: T }
 { -readonly [ P in K ] ?: T }
Partial type
type MyPartial<User> = { [P in keyof User]?: User[P] }
// usage
type User = {
  name: string
  password: string
  address: string
  phone: string
}

const partialUserType: MyPartial<User> = {
  name: 'Sten',
  password: '1234',
}
All partial except specified properties

Make all properties optional except

type partialExcept = Partial<HslObject> & Pick<HslObject, 'hue'>
PartialBy

Create optional except specified

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
type HslObjectLightness = PartialBy<HslObject, 'saturation' | 'hue'>
Getter type. (using the ‘as’ keyword available since ts 4.1)

Creates a new key out of the current key string. Symbol and number keys are filtered out with the & opperator.

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
interface Person {
  name: string
  age: number
  location: string
}
type LazyPerson = Getters<Person>
// {
//   getName: () => string;
//   getAge: () => number;
//   getLocation: () => string;
// }
Remove type of field
type RemoveKindFiled<T> = {
  [K in keyof T as Exclude<K, 'kind'>]: T[K]
}
type Exclude<T, U> = T extends U ? never : T
// usage
type KindlessCircle = RemoveKindField<circle>

interface Circle {
  kind: 'circle'
  radius: number
}
// ==> RemoveKindField<Circle>
{
  radius: number
}
Conditional types
T extends U ? X : Y
// Read as When T can be assigned to type U then return X, else Y

// usage
type IsString<T> = T extends string ? true : false;

type I0 = IsString<number>;  // false
type I1 = IsString<"abc">;  // true
type I2 = IsString<any>;  // boolean
type I3 = IsString<never>;  // never
Mixing up mapped types with conditionals, examples
type FunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? K : never
}[keyof T]
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>
type NonFunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? never : K
}[keyof T]
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>
interface User {
  id: number
  name: string
  age: number
  updateName(newName: string): void
}
type T5 = FunctionPropertyNames<User> // "updateName"
type T6 = FunctionProperties<User> // { updateName: (newName: string) => void; }
type T7 = NonFunctionPropertyNames<User> // "id" | "name" | "age"
type T8 = NonFunctionProperties<User> // { id: number; name: string; age: number; }
Instead of enums use this

Instead of doing types for your data, generate type from your data.

const weights = {
  heavy: 900,
  light: 200,
} as const // narrows the type from number to actual value

type Weights = typeof weights;

type WeightKey = keyof Weights; // wil get you a union > "heavy" | "light"

// iterate over that object and grab you a union of the actual value
type WeightValue = (typeof weights)[WeightKey]; // > 900 | 200

function styleText(weight: weightKey | WeightValue) {
  console.log(message: weight);
}

styleText(weights.heavy); // paste in like an enum
styleText('heavy'); // paste in values directly
styleText('light');
styleText(900);
Instead of union use this
const roles = ['user', 'admin', 'superadmin'] as const

// Roles is now an array, not 'user' | 'admin' | 'superadmin'
type RolesAsType = typeof roles

// We can get the array members by manually passing each index to the type
type Role = RolesAsType[1 | 2 | 3]

// or we can pass in number to extract every member of the array
type Role = RolesAsType[number]

// Finally we can do a typeguard function
const isRole = (RoleToCheck: string): RoleToCheck is Role =>
  roles.some((role) => role === roleToCheck)
Keyof object but for values

Doing a keyof typeof myObject is great for creating an union type out of object keys. Here is how you do the same for values.

// Create a valueOf ts-function helper...
type ValueOf<T> = T[keyof T]

// ...or just inline (and admitadly more readable introspect)
type MyValueUnion = (typeof weights)[keyof WeightKey]
Need to get rid of that undefined?

Got an incomming argument that is optional but you know it will be set further in? Narrow out that undefined.

function assertMember(arg: unknown): asserts arg is Member {
  if(!arg ||
    typeof arg !== 'object' ||
    !('id' in arg) ||
    typeof arg.id !== 'number') {
      throw new Error()
    }
}

assertMember(memberObj) // below memberObj is without undefined

...

Or if your target is a primitve

function isNotNull<T>(arg: T | null | undefined): arg is T {
  return !!arg
}

…but personally I had most luck with this function:

function assertIsDefined<T>(value: T): asserts value is NonNullable<T> {
  if (value === undefined || value === null) {
    throw new Error(`${value} is not defined`)
  }
}

or

const raise = (err: string): never => {
  throw new Error(err)
}

now you can

const Page = (props: {
  params: {
    id?: string
  }
}) => {
  const id = props.params.id ?? raise('No id provided')
}

and id is infered to as a string, not string | undefined.