13

There's a similar question but it's a different problem from mine found here Argument of type '...' is not assignable to parameter of type '...' TS 2345

utils.ts (https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-types)

export function getRandomItem<T>(array: T[]): T {
  return array[Math.floor(Math.random() * array.length)]
}

apparel.ts

import { getRandomItem } from "@/assets/ts/utils"

export const apparelLocation = ["HEAD", "TORSO", "ARMS", "LEGS"] as const
export type TypeApparelLocation = typeof apparelLocation[number]

// ...

export class Apparel {
  / ...
  location: TypeApparelLocation

  constructor(rating: number) {
    // ...
    this.location = getRandomItem(apparelLocation)
    }
  }

Will give an error inside when using the getRandomItem()

Argument of type 'readonly ["HEAD", "TORSO", "ARMS", "LEGS"]' is not assignable to parameter of type '("HEAD" | "TORSO" | "ARMS" | "LEGS")[]'. The type 'readonly ["HEAD", "TORSO", "ARMS", "LEGS"]' is 'readonly' and cannot be assigned to the mutable type '("HEAD" | "TORSO" | "ARMS" | "LEGS")[]'.ts(2345)

What I'm trying to do (in case needed for a better understanding):

  1. create a variable containing an array with literal values
  2. create a type declaration (an union of literal) from that array (TypeScript: Define a union type from an array of strings)
  3. use that type as an annotation on a class attribute
  4. select a random element from the array to assign to the location attribute

As to why I need the first reason is because I need do a loop somewhere else.

Few "fixes" I found:

  1. removing as const from the apparelLocation made it work but I can assign any value to location and not just those 4
  2. removing type annotation on the function and use a plain array: any also works but it's giving a warning

Apologies if it's an obvious mistake by me since I'm relatively new to typescript.

ArnNied
  • 173
  • 1
  • 1
  • 8

2 Answers2

13

Unless someone proves me wrong, what I understood is that typescript is complaining that you're passing an immutable array to the function, because the function parameter array is mutable and can be edited, while the value you're passing is a constant.

The better solution is to setup the function paramater as readonly:

function getRandomItem<T>(array: readonly T[]): T {
   // ...
}

By setting the parameter as readonly, typescript won't complain anymore because you won't be able to modify it inside the function.

Another, less nice, solution could be editing the call like this:

this.location = getRandomItem([...apparelLocation])

This way, you're not passing the original, immutable array, but just a copy of it that can be handled and is coherent with the function parameter being mutable.

Drago96
  • 1,265
  • 10
  • 19
  • 1
    Oh my god thank you, i've been stuck here for the day. I haven't sunk much time to js, even more so ts. Just another thing, is there a better way of implementing of what I'm trying to do compared to mine? Since I arrived with mine from bits and pieces from the internet. I've read about `enum` but i need to do `Object.values()` to get the value if i use string for the value not just the key(?) – ArnNied Apr 23 '22 at 11:30
  • To be honest, I don't have a lot of experience with typescript either :) However, if you need to get a random value, it makes sense to have everything in an array and use it the way you did. Enums aren't real javascript objects, from what i know, so you won't be able to select a random value from them – Drago96 Apr 23 '22 at 12:44
0

If you don't want to use generic type you can add readonly type like me:

generateNavigations(router.options.routes)

const generateNavigations = (localRoutes: readonly RouteRecordRaw[]) => {
  // your staff
}
Nimble Stalker
  • 307
  • 3
  • 14