mobx-state-tree icon indicating copy to clipboard operation
mobx-state-tree copied to clipboard

Take a readonly array when possible

Open bengry opened this issue 6 years ago • 5 comments

Bug report

  • [x] I've checked documentation and searched for existing issues
  • [x] I've made sure my project is based on the latest MST version
  • [x] Fork this code sandbox or another minimal reproduction.

Sandbox link or minimal reproduction code

type TodoType = "task" | "bug";
// written as a separate variable for other references that are not tied to MST
const todoTypes: ReadonlyArray<TodoType> = ["task", "bug"];
const todoTypes: ["task", "bug"] as const // same as above, but shorter, for TS >= 3.4

export const Todo = types
  .model("Todo", {
    id: types.optional(types.number, () => Math.random()),
    title: types.string,
    finished: false,
    type: types.enumeration(todoTypes) // error
  })

Describe the expected behavior The above code should compile

Describe the observed behavior Line 10 shows the following error:

Argument of type 'ReadonlyArray<TodoType>' is not assignable to parameter of type 'TodoType[]'. Type 'ReadonlyArray<TodoType>' is missing the following properties from type 'TodoType[]': pop, push, reverse, shift, and 6 more

This is due to enumeration accepting T[], and not a ReadonlyArray<T>/readonly T[] (latter as of TS 3.4).

This behavior is not just for this case, but also in other places in MST (e.g. types.union.

My suggestion is to accept the minimal necessary interface for arrays when possible. This should cover most, if not all cases, since they are usually not directly used anyway, at least for types. This is more important with TypeScript 3.4+ since it's now much less of a hassle to make an array a ReadonlyArray, especially with as const, but also with the readonly modifier added to more use-uses (e.g. function parameters).

If this is handled, upgrading to TypeScript 3.4 (or greater) should be considered, since it makes the changes easier to type. e.g.:

// TS 3.3
export function enumeration<T extends string>(options: ReadonlyArray<T>): ISimpleType<UnionStringArray<T[]>>
export function enumeration<T extends string>(
    name: string,
    options: ReadonlyArray<T>
): ISimpleType<UnionStringArray<T[]>>

// TS 3.4
export function enumeration<T extends string>(options: readonly T[]): ISimpleType<UnionStringArray<T[]>>
export function enumeration<T extends string>(
    name: string,
    options: readonly T[]
): ISimpleType<UnionStringArray<T[]>>

At the cost of supporting only TS 3.4+ (maybe we can transpile these to ReadonlyArray in the output type declarations?)

bengry avatar Jun 11 '19 15:06 bengry

I usually use this pattern in ts with enumerations

enum Color {
  Red = "red",
  Green = "green"
}

const colorType = types.enumeration<Color>(Object.values(Color))

const myModel = types.model({
  color: colorType,
})

xaviergonz avatar Jun 12 '19 18:06 xaviergonz

But that being said, yeah, it should take a ReadonlyArray<T> to not leave old ts users out

xaviergonz avatar Jun 12 '19 18:06 xaviergonz

When initializing a model with arrays, it also doesn't accept read-only arrays. Is that because the array instance is reused?

mbest avatar Apr 22 '20 17:04 mbest

I think that i just an omission in the typings and should be safe.

On Wed, Apr 22, 2020 at 6:03 PM Michael Best [email protected] wrote:

When initializing a model with arrays, it also doesn't accept read-only arrays. Is that because the array instance is reused?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx-state-tree/issues/1314#issuecomment-617906533, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAN4NBFRWIO732NYX6UZ67TRN4PNTANCNFSM4HW7I4QA .

mweststrate avatar Apr 22 '20 17:04 mweststrate

Here's another (AFAIK most DRY) way to use your readonly arrays for union types and as input for an enumeration type, just use array spread:

const myThings = ["foo", "bar"] as const
type MyThingsType = typeof myThings[number] // "foo" | "bar" 

types.model({
  //array spread makes a non-readonly copy so types.enumeration is happy
   myThingsEnum: types.enumeration([...myThings]) 
})

evelant avatar Feb 20 '21 17:02 evelant

New PR created to fix the types for this: #2059.

jamonholmgren avatar Aug 09 '23 22:08 jamonholmgren