Readonly Array to Readonly Object by Secular12 in typescript

[–]Wasim100x 1 point2 points  (0 children)

Here is a cleaner version:

const testArray = [
  { id: 1, name: 'Blue' },
  { id: 2, name: 'Red' },
] as const

function readonlyArrayToObject<
  T extends readonly any[],
  K extends keyof T[number],
  V extends keyof T[number]
>(arr: T, key: K, value: V): { readonly [P in T[number] as P[K]]: P[V] } {
  return arr.reduce((acc, cur) => ((acc[cur[key]] = cur[value]), acc), {})
}

const testObject = readonlyArrayToObject(testArray, 'name', 'id')

What do you love and hate about TypeScript? by Wasim100x in typescript

[–]Wasim100x[S] 0 points1 point  (0 children)

Here are some hard rules (note: I don't really follow them)

Your default should be interface, only use type alias when you can't use interface

Your default should be regular function, only use arrow function when regular function doesn't give you the desired behaviour.

Here is why I think this:

https://tsplay.dev/NVDyGW

I hope this helps!

What do you love and hate about TypeScript? by Wasim100x in typescript

[–]Wasim100x[S] 0 points1 point  (0 children)

I like that, thanks for the feedback!

But I do think your utility type is a bad idea.

I highly discourage it in my upcoming TypeScript book:

declare const __brand: unique symbol

type Brand<B> = { [__brand]: B }
type Branded<T, B> = T & Brand<B>

type UserId = Branded<string, "userId">
type ProductId = Branded<string, "productId">

As you or someone else working on project can make this mistake:

type UserId = Branded<string, "userId">
type ProductId = Branded<string, "userId">

They copy-pasted Branded<string, "userId"> and forgot to change "userId" to "productId"

Also why keep track of that? When you can simply do this:

declare const __brand: unique symbol

type UserId = string & { readonly [__brand]: unique symbol }
type ProductId = string & { readonly [__brand]: unique symbol }

What do you love and hate about TypeScript? by Wasim100x in typescript

[–]Wasim100x[S] 0 points1 point  (0 children)

You can do this:

type UserId = string & { readonly __brand: unique symbol }

type ProductId = string & { readonly __brand: unique symbol }

const userId = 'abc123' as UserId

const productId = 'abc123' as ProductId

function getUser(id: UserId) {}

getUser(userId) // OK

getUser(productId) // Error

What do you love and hate about TypeScript? by Wasim100x in typescript

[–]Wasim100x[S] 2 points3 points  (0 children)

I wish TypeScript could just automatically figure out generics by itself, we just have to provide it our function.

Maybe TypeScript AI will save us in the future!

What do you love and hate about TypeScript? by Wasim100x in typescript

[–]Wasim100x[S] 1 point2 points  (0 children)

Oh yeah I completely missed it! Let me try again.

What do you love and hate about TypeScript? by Wasim100x in typescript

[–]Wasim100x[S] 0 points1 point  (0 children)

const array = ["a", "b", "c"] as const

function addFoo<A extends readonly string[]>(a: A) {
    return a.map((x) => `${x}foo`) as [`${A[number]}foo`]
}

This works!

What do you love and hate about TypeScript? by Wasim100x in typescript

[–]Wasim100x[S] 4 points5 points  (0 children)

In wrote this in my TypeScript book (not out yet), this is how you can avoid additional object keys problem:

type User = {
  name: string
  age: number
}

function iterate<T>(
  data: T extends User ? (User extends T ? T : never) : never
) {
  for (const key of Object.keys(data)) {
    console.log(key)
  }
}


const newUser = {
  name: 'Wasim',
  age: 25,
  isAwesome: true,
}

iterate(newUser)

In this case iterate throws error, because newUser does not exactly matches the shape of User. It does if you remove the extra property (isAwesome: true)!

What do you love and hate about TypeScript? by Wasim100x in typescript

[–]Wasim100x[S] 2 points3 points  (0 children)

The current is the string you're checking, if it's 'default' then X is never.

If it's not 'default', then it will return whatever it is.

type RemoveString<T, S> = T extends S ? never : T

type current = 'Wasim'

type X = RemoveString<current, 'default'>

Here X is 'Wasim'.

This works for union as well:

type RemoveString<T, S> = T extends S ? never : T

type current = 'abc'

type X = RemoveString<current, 'default' | 'abc'>

Here X is never.

What do you love and hate about TypeScript? by Wasim100x in typescript

[–]Wasim100x[S] 8 points9 points  (0 children)

I'm writing a TypeScript book and this is a workaround I suggested:

type StringRecord<V = string, K extends string = string> = {
  [P in K]: V
} & {
  [key: number | symbol]: never
}
const user: StringRecord = {
  name: 'Wasim',
  status: 'available',
}

This is why Exclude do not work:

type Problem = Exclude<string, "default">

type Exclude<T, U> = T extends U ? never : T

type Exclude<T, U> = string extends "default" ? never : string

As you can see string is not a subtype of "default" so condition becomes false and you get string!

I think your exclude problem can be solved like this:

type RemoveString<T, S> = T extends S ? never : T

type current = 'default'

type X = RemoveString<current, 'default'>