Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typescript error when using Array.prototype.filter or spread-operator #957

Closed
misantronic opened this issue Aug 2, 2018 · 3 comments
Closed
Labels
not a bug Seems like a bug, but actually it is not

Comments

@misantronic
Copy link

I am using mobx-state-tree 3.02 and mobx 5.0.3
typescript 3.0.1

when filtering or reassigning an array via spread, I get a typescript error:

Type 'ModelInstanceType<ModelPropertiesDeclarationToProperties<{ name: ISimpleType<string>; }>, {}, ModelCreationType<ModelPropertiesDeclarationToProperties<{ name: ISimpleType<string>; }>>, ModelSnapshotType<...>>[]' is not assignable to type 'IMSTArray<ModelCreationType<ModelPropertiesDeclarationToProperties<{ name: ISimpleType<string>; }>>, ModelSnapshotType<ModelPropertiesDeclarationToProperties<{ name: ISimpleType<string>; }>>, ModelInstanceType<...>> & IStateTreeNode<...>'.
  Type 'ModelInstanceType<ModelPropertiesDeclarationToProperties<{ name: ISimpleType<string>; }>, {}, ModelCreationType<ModelPropertiesDeclarationToProperties<{ name: ISimpleType<string>; }>>, ModelSnapshotType<...>>[]' is not assignable to type 'IMSTArray<ModelCreationType<ModelPropertiesDeclarationToProperties<{ name: ISimpleType<string>; }>>, ModelSnapshotType<ModelPropertiesDeclarationToProperties<{ name: ISimpleType<string>; }>>, ModelInstanceType<...>>'.
    Property 'spliceWithArray' is missing in type 'ModelInstanceType<ModelPropertiesDeclarationToProperties<{ name: ISimpleType<string>; }>, {}, ModelCreationType<ModelPropertiesDeclarationToProperties<{ name: ISimpleType<string>; }>>, ModelSnapshotType<...>>[]'.

due to the complexity of the typings I cannot read anything from this.

const Platform = types.model('Platform', {
    name: types.string
});

export const PlatformStore = types
    .model('PlatformStore', {
        platforms: types.array(Platform)
    })
    .actions(self => ({
        add: (name: string) => {
            // error
            self.platforms = [...self.platforms, { name }];
        },

        remove: (name: string) => {
            // error
            self.platforms = self.platforms.filter(
                platform => platform.name !== name
            );
        }
    }));
@xaviergonz
Copy link
Contributor

xaviergonz commented Aug 2, 2018

That's because sadly TS doesn't support different types for property getters and property setters
Since they have to be the same, the getter/setter is set to an instance (type/node) rather than the snapshot/creation type. If the property was typed as Type | CreationType then all the properties unique to the Type (e.g. actions, views, etc) wouldn't be available when trying to use the property.

Sadly, to overcome this you have to either cast it to the instance type (since internally mst will convert the snapshot to an instance) or create the instance directly.
e.g.

const Platform = types.model("Platform", {
    name: types.string
})

const PlatformArray = types.array(Platform)

export const PlatformStore = types
    .model("PlatformStore", {
        platforms: PlatformArray
    })
    .actions(self => ({
        add: (name: string) => {
            // choose one
            self.platforms = [...self.platforms, { name }] as typeof PlatformArray.Type

            self.platforms = PlatformArray.create([...self.platforms, { name }])
        },

        remove: (name: string) => {
            // choose one
            self.platforms = self.platforms.filter(platform => platform.name !== name) as typeof PlatformArray.Type

            self.platforms = PlatformArray.create(self.platforms.filter(platform => platform.name !== name))
        }
    }))

The proposal I wrote in #955 is to overcome this issue. If you have further ideas on how to improve this please feel free to contribute there 👍

@xaviergonz xaviergonz added question Question or help request not a bug Seems like a bug, but actually it is not and removed question Question or help request labels Aug 2, 2018
@JoshMoreno
Copy link

If it helps...

Current solution is to use cast - docs
Which will cast to a type instance and fool typescript

+ import {cast} from 'mobx-state-tree'

const Platform = types.model('Platform', {
    name: types.string
});

export const PlatformStore = types
    .model('PlatformStore', {
        platforms: types.array(Platform)
    })
    .actions(self => ({
        add: (name: string) => {
-            // error
-            self.platforms = [...self.platforms, { name }];
+            self.platforms = cast([...self.platforms, { name }]);
        },

        remove: (name: string) => {
            // error
            self.platforms = self.platforms.filter(
                platform => platform.name !== name
            );
        }
    }));

@JoshMoreno
Copy link

JoshMoreno commented Dec 4, 2018

Just ran into another gotcha when trying to filter an array which contained a model with a map of a union. Sample below.

Not sure if this is a bug or not, but there is a workaround. See below. Attemp #1 and #2 are to illustrate where I was hitting the roadblocks.

Possibly related - #913

import {cast, types} from 'mobx-state-tree'

const Swing = types.model({})
const Slide = types.model({})
const Playground = types.model({
	id: types.identifier,
	items: types.map(types.union(Swing, Slide))
})

const Store = types.model({
		playgrounds: types.array(Playground)
	})
	.actions(self => ({
		filterItems() {
                        // attempt #1
			self.playgrounds = self.playgrounds.filter(item => item.id !== 'something')
			// attempt #2
			self.playgrounds = cast(self.playgrounds.filter(item => item.id !== 'something'))
		}
	}))

Attempt #1

Typescript complains that it's not no longer an observable array
Okay, no problem, just forgot to cast it.

Error:(16, 4) TS2740: Type 'ModelInstanceType<ModelPropertiesDeclarationToProperties<{ id: ISimpleType<string>; items: IMapType<ITypeUnion<ExtractCFromProps<RequiredPropsObject<ModelPropertiesDeclarationToProperties<{}>> & OptionalPropsObject<ModelPropertiesDeclarationToProperties<{}>>> | ExtractCFromProps<...>, ModelSnapshotType<...> | ModelS...' is missing the following properties from type 'IMSTArray<IModelType<ModelPropertiesDeclarationToProperties<{ id: ISimpleType<string>; items: IMapType<ITypeUnion<ExtractCFromProps<RequiredPropsObject<ModelPropertiesDeclarationToProperties<{}>> & OptionalPropsObject<ModelPropertiesDeclarationToProperties<{}>>> | ExtractCFromProps<...>, ModelSnapshotType<...> | Mod...': spliceWithArray, observe, intercept, clear, and 6 more.

Attempt #2

Oh no! We got an Index signature is missing in type error.

Error:(18, 28) TS2345: Argument of type 'ModelInstanceType<ModelPropertiesDeclarationToProperties<{ id: ISimpleType<string>; items: IMapType<ITypeUnion<ExtractCFromProps<RequiredPropsObject<ModelPropertiesDeclarationToProperties<{}>> & OptionalPropsObject<ModelPropertiesDeclarationToProperties<{}>>> | ExtractCFromProps<...>, ModelSnapshotType<...> | ModelS...' is not assignable to parameter of type 'ExtractCFromProps<RequiredPropsObject<ModelPropertiesDeclarationToProperties<{ id: ISimpleType<string>; items: IMapType<ITypeUnion<ExtractCFromProps<RequiredPropsObject<ModelPropertiesDeclarationToProperties<{}>> & OptionalPropsObject<ModelPropertiesDeclarationToProperties<{}>>> | ExtractCFromProps<...>, ModelSnapsh...'.
  Type 'ModelInstanceType<ModelPropertiesDeclarationToProperties<{ id: ISimpleType<string>; items: IMapType<ITypeUnion<ExtractCFromProps<RequiredPropsObject<ModelPropertiesDeclarationToProperties<{}>> & OptionalPropsObject<ModelPropertiesDeclarationToProperties<{}>>> | ExtractCFromProps<...>, ModelSnapshotType<...> | ModelS...' is not assignable to type 'ExtractCFromProps<RequiredPropsObject<ModelPropertiesDeclarationToProperties<{ id: ISimpleType<string>; items: IMapType<ITypeUnion<ExtractCFromProps<RequiredPropsObject<ModelPropertiesDeclarationToProperties<{}>> & OptionalPropsObject<ModelPropertiesDeclarationToProperties<{}>>> | ExtractCFromProps<...>, ModelSnapsh...'.
    Type 'ModelInstanceType<ModelPropertiesDeclarationToProperties<{ id: ISimpleType<string>; items: IMapType<ITypeUnion<ExtractCFromProps<RequiredPropsObject<ModelPropertiesDeclarationToProperties<{}>> & OptionalPropsObject<ModelPropertiesDeclarationToProperties<{}>>> | ExtractCFromProps<...>, ModelSnapshotType<...> | ModelS...' is not assignable to type 'ExtractCFromProps<RequiredPropsObject<ModelPropertiesDeclarationToProperties<{ id: ISimpleType<string>; items: IMapType<ITypeUnion<ExtractCFromProps<RequiredPropsObject<ModelPropertiesDeclarationToProperties<{}>> & OptionalPropsObject<ModelPropertiesDeclarationToProperties<{}>>> | ExtractCFromProps<...>, ModelSnapsh...'.
      Types of property 'items' are incompatible.
        Type 'IMSTMap<ITypeUnion<ExtractCFromProps<RequiredPropsObject<ModelPropertiesDeclarationToProperties<{}>> & OptionalPropsObject<ModelPropertiesDeclarationToProperties<{}>>> | ExtractCFromProps<...>, ModelSnapshotType<...> | ModelSnapshotType<...>, ModelInstanceType<...> | ModelInstanceType<...>>>' is not assignable to type 'IKeyValueMap<ExtractCFromProps<RequiredPropsObject<ModelPropertiesDeclarationToProperties<{}>> & OptionalPropsObject<ModelPropertiesDeclarationToProperties<{}>>> | ExtractCFromProps<RequiredPropsObject<...> & OptionalPropsObject<...>>>'.
          Index signature is missing in type 'IMSTMap<ITypeUnion<ExtractCFromProps<RequiredPropsObject<ModelPropertiesDeclarationToProperties<{}>> & OptionalPropsObject<ModelPropertiesDeclarationToProperties<{}>>> | ExtractCFromProps<...>, ModelSnapshotType<...> | ModelSnapshotType<...>, ModelInstanceType<...> | ModelInstanceType<...>>>'.

Solution

Wrap the map in a types.maybe() and cast as Attempt #2 showed..

- items: types.map(types.union(Swing, Slide))
+ items: types.maybe(types.map(types.union(Swing, Slide)))

- // Attempt #1
- self.playgrounds = self.playgrounds.filter(item => item.id !== 'something')
// Attempt #2
self.playgrounds = cast(self.playgrounds.filter(item => item.id !== 'something'))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
not a bug Seems like a bug, but actually it is not
Projects
None yet
Development

No branches or pull requests

3 participants