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

Self reference model with Typescript and MST v3 #943

Closed
AndrewSorokin opened this issue Jul 23, 2018 · 24 comments
Closed

Self reference model with Typescript and MST v3 #943

AndrewSorokin opened this issue Jul 23, 2018 · 24 comments
Labels
can't fix Cannot be fixed

Comments

@AndrewSorokin
Copy link

AndrewSorokin commented Jul 23, 2018

I have some issue with explicit model typing in v3.

I need to get model with self reference with typescript.
There is solution #417 for MST v2 smth. like that:


interface IModel {
    key: string;
    reference: IModel;

    prop: number;
}
export const Model: IModelType<ISnapshottable<IModel>, IModel> = types.model({
    key: types.identifier,
    reference: types.late(() => Model),

    prop: types.number
})

What is a proper way to express model type with MST v3?

@AndrewSorokin AndrewSorokin changed the title Self referrence model with Typescript and MST v3 Self reference model with Typescript and MST v3 Jul 23, 2018
@AndrewSorokin
Copy link
Author

AndrewSorokin commented Jul 23, 2018

The only ugly way I was able to do this is there:

import {
    types, IModelType, IComplexType
} from 'mobx-state-tree';

const ModelProps = {
    key: types.identifier,
    num: types.number,
    ref: types.frozen() as IComplexType<IModel | string | number, string | number, IModel>,
};


interface IModel {
    method: (param: number) => string, 

    key: string,
    num: number,
    ref: IModel,
}


export const Model: IModelType<typeof ModelProps, IModel> = types.model({
            ...ModelProps,
            ref: types.reference(types.late(() => Model))
        })
    .actions(self => ({
        method: (t: number) => { return t.toString(); }
    }))

export const m = Model.create({
    key: '1',
    num: 0,
    ref: '1'
});

@AndrewSorokin
Copy link
Author

Another aproach is to manualy resolve references.

Still waiting for comments.

import {
    types, getRoot, resolveIdentifier
} from 'mobx-state-tree';



export const Model = types.model(
        {
            key: types.identifier,
            num: types.number,
            refKey: types.string
        })
        .views(self => ({
            get ref() {
                const root = getRoot(self);
                const ref = resolveIdentifier(Model , root, self.refKey);
                return ref as typeof Model.Type;
            }
        }))
    .actions(self => ({
        method: (t: number) => { 
            return t.toString(); 
        }
    }))

export const m = Model.create({
    key: '1',
    num: 0,
    refKey: '1'
});

@mweststrate
Copy link
Member

mweststrate commented Jul 25, 2018 via email

@mweststrate mweststrate added the needs reproduction/info Needs more info or reproduction in order to fix it label Jul 26, 2018
@AndrewSorokin
Copy link
Author

AndrewSorokin commented Jul 26, 2018

Sure, here is it.

Example with manual reference resolution
https://codesandbox.io/s/20wlz7256j

I don't like that because of redundancy and some extra work I need to do.

Example with explicit typings:
https://codesandbox.io/s/jl5jkywyk3

It looks a bit ugly.
Is there any more elegant way to explicilly define model type with self reference?

@mweststrate
Copy link
Member

There seems to be no clean way to fix, as TS really can't handle the type of something self referring. Even upcasting doesn't work. However, using a temporary variable seems to work around it:

let tmp: any

///  BrokenTypingsModel has any type or leads to TS compiler error
const BrokenTypingsModel = types.model({
    key: types.identifier,
    brokenRef: types.maybe(types.reference(types.late<BrokenTypeLike, BrokenTypeLike, BrokenTypeLike>(() => tmp)))
})

interface BrokenTypeLike {
    key: string
    brokenRef: BrokenTypeLike
}
tmp = BrokenTypingsModel

const b = BrokenTypingsModel.create({ key: '3' })
console.log(b.brokenRef ? b.brokenRef.key : 'nothing')

https://codesandbox.io/s/nknnl9zl2m

@bourquep
Copy link

Is the problem described here similar to the one described in this issue?

@mweststrate
Copy link
Member

@bourquep no that is a different problem, the issue here is like expressing a self-referring structure interface Node { children: Node[] } . However, the issue you are referring to is actions that refer to their own structural type, which we can express because models are built up in parts (and actions can be defined in local closures first) as described here (not ideal, but possible): https://github.com/mobxjs/mobx-state-tree#typing-self-in-actions-and-views)

@mweststrate mweststrate added can't fix Cannot be fixed and removed needs reproduction/info Needs more info or reproduction in order to fix it labels Aug 27, 2018
@mweststrate
Copy link
Member

Closing as a can't fix at this moment :-(

@xaviergonz
Copy link
Contributor

actually using 'this' for actions defined in the same scope is also possible (like for views), maybe the readme should be updated with that?

@mweststrate
Copy link
Member

mweststrate commented Aug 27, 2018 via email

@nspaeth
Copy link

nspaeth commented Sep 4, 2018

I'm having trouble getting the workarounds using late working in newer versions, and the code sandbox examples also don't seem to work with newer versions.

That said, there seems to be a couple of new solutions for circular references using conditional types. See This Comment and this Comment in the TypeScript repo. Would either of these be applicable here?

@kresli
Copy link

kresli commented Nov 28, 2018

I can confirm late is broken in TS. At least I didn't found any solution how to properly typesafe. Even inline docs doesn't work

* childs: types.optional(types.array(types.late<any, INode>(() => Node)), [])

@xaviergonz
Copy link
Contributor

I'm not sure if you are using late to fix cicular/self-references, but if so, from the docs:

If you are using TypeScript and you get errors about circular or self-referencing types then you can partially fix it by doing:

const Node = types.model({
    x: 5, // as an example
    me: types.maybe(types.late((): IAnyModelType => Node))
})

In this case, while "me" will become any, any other properties (such as x) will be strongly typed, so you can typecast the self referencing properties (me in this case) once more to get typings. For example:

node.((me) as Instance<typeof Node>).x // x here will be number

@kresli
Copy link

kresli commented Nov 28, 2018

thanks @xaviergonz I hoped there will be a better way. I end up using views

const Node = types.model({
    x: 5, 
    me: types.maybe(types.late((): IAnyModelType => Node))
}).views( self => ({
    get meProp() {
        return self.me as Instance<typeof Node>;
    }
}))

@xaviergonz
Copy link
Contributor

Nice trick! Never thought of adding a view afterwards to actually make its type proper again!

@elie222
Copy link

elie222 commented Feb 12, 2019

Worth adding this trick to TS FAQ

@mweststrate
Copy link
Member

@elie222 PR welcome!

@elie222
Copy link

elie222 commented Feb 12, 2019

Sure. Just had a look here and I see the Edit on GitHub link is broken:

https://mobx-state-tree.gitbook.io/docs/faq

Where is the repo for these docs?

@elie222
Copy link

elie222 commented Feb 12, 2019

Sorry, will just update the official README

@elie222
Copy link

elie222 commented Feb 12, 2019

@cevek
Copy link

cevek commented Jun 4, 2019

some possible solution:

var User = struct({
    user: () => User,
});

User.user.user.user // ok

function struct<T>(obj: T) {
    return obj as {[P in keyof T]: T[P] extends () => infer R ? R : T[P]};
}

beepsoft added a commit to beepsoft/mst-gql that referenced this issue Sep 4, 2019
Implements the trick mentioned in this comment:

mobxjs/mobx-state-tree#943 (comment)

Every late() reference field will be of type IAnyModelType and will be
accompanied by a view getter casting it to the actual type of the prop.
After this a reference field name "refField" should be accessed as
"refFieldProp" which will provide correct typings/autocomplete in IDEs.

This trick works up to typescript 3.4.x and doesn't work in 3.5 and
above :-( Using Instance<...> like this however still works in all
versions:

mobxjs/mobx-state-tree#943 (comment)

refs mobxjs#45
@ArmorDarks
Copy link

@kresli Isn't it results in the same error about the self-referenced variable? It's still part of the same declaration, so TS will complain that it can't infer its type, even despite it's in a views clause

@kresli
Copy link

kresli commented Nov 24, 2019

@ArmorDarks No it shouldn't because the model part would complain about circulation. So in the getter you would type model where me prop in model is any. therefor there is no circulation.

@mweststrate
Copy link
Member

Please don't reply on closed issues

@mobxjs mobxjs locked as resolved and limited conversation to collaborators Nov 27, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
can't fix Cannot be fixed
Projects
None yet
Development

No branches or pull requests

9 participants