Mobx-state-tree: How to type safe call of action from another action

Created on 24 Aug 2018  Â·  18Comments  Â·  Source: mobxjs/mobx-state-tree

I have:

  • [x] A conceptual question.

    • [x] I've checked documentation and searched for existing issues

    • [ ] I tried the spectrum channel first

  • [x] I think something is not working as it should.

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

      I want to call action from another action in Typescript.
      The only way to do it is via (self as any).anotherAction() as "self" is only typed for props in action.
      Is there any other way?

    • [ ] Describe observed behavior
      _Not following the above template might result in your issue being closed without further notice_

  • [ ] Feature request

    • [ ] Describe the feature and why you feel your feature needs to be generically solved inside MST

    • [ ] Are you willing to (attempt) a PR?

question

Most helpful comment

unit test used to make sure it was right

    const Example = types.model("Example", { prop: types.string }).views(self => ({
        get upperProp(): string {
            return self.prop.toUpperCase()
        },
        get twiceUpperProp(): string {
            return this.upperProp + this.upperProp
        }
    }))

    const EI = Example.create({ prop: "hi" })
    expect(EI.upperProp).toBe("HI")
    expect(EI.twiceUpperProp).toBe("HIHI")
    expect(isObservableProp(EI, "upperProp")).toBe(true)
    expect(isObservableProp(EI, "twiceUpperProp")).toBe(true)

All 18 comments

For now one of the alternatives is separating them

types.model({x: 5})
.actions(self => ({
  a1() {}
})
.actions(self => ({
  a2() {
    self.a1()
  }
}))

another alternative is (don't use this trick for views since if you do views won't be computed)

types.model({x: 5})
.actions(self => {
  const actions = {
    a1() {},
    a2() { actions.a1() }
  }
  return actions
})

for new alternatives being proposed please check https://github.com/mobxjs/mobx-state-tree/issues/982

See also: https://github.com/mobxjs/mobx-state-tree#typing-self-in-actions-and-views

Thank you all for your answers. I have A LOT of different approaches. I'm sorry I missed that bit in the documentation. I read it quite a few times ...

@mweststrate I tried to use the following approach from https://github.com/mobxjs/mobx-state-tree#typing-self-in-actions-and-views but TypeScript 3 doesn't like it. I think Example becomes any if referenced inside its initialization.

const Example = types
  .model("Example", { prop: types.string })
  .views((self: typeof Example.Type) => ({
      // use typeof instead of predefined type to avoid circular references
      get upperProp(): string {
          return self.prop.toUpperCase()
      },
      get twiceUpperProp(): string {
          return self.upperProp + self.upperProp
      }
  }));

The error is:
[ts] 'Example' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

@malekpour
try this instead

    const Example = types
        .model("Example", { prop: types.string })
        .views((self) => ({
            get upperProp(): string {
                return self.prop.toUpperCase()
            }
        }))
        .views((self) => ({
            get twiceUpperProp(): string {
                return self.upperProp + self.upperProp
            }
        }));

or alternatively this

const Example = types.model("Example", { prop: types.string }).views(self => ({
        get upperProp(): string {
            return self.prop.toUpperCase()
        },
        get twiceUpperProp(): string {
            return this.upperProp + this.upperProp
        }
    }))

@malekpour just fixed the readme with the "this" approach rather than the incorrect typeof one, thanks for pointing it out!

unit test used to make sure it was right

    const Example = types.model("Example", { prop: types.string }).views(self => ({
        get upperProp(): string {
            return self.prop.toUpperCase()
        },
        get twiceUpperProp(): string {
            return this.upperProp + this.upperProp
        }
    }))

    const EI = Example.create({ prop: "hi" })
    expect(EI.upperProp).toBe("HI")
    expect(EI.twiceUpperProp).toBe("HIHI")
    expect(isObservableProp(EI, "upperProp")).toBe(true)
    expect(isObservableProp(EI, "twiceUpperProp")).toBe(true)

Strange, in action, this for me is "window".

And this type is any for me.

Type inference depends on the compiler settings, so they might differ

Op di 28 aug. 2018 00:31 schreef Ali Malekpour notifications@github.com:

And this type is any for me.

—
You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
https://github.com/mobxjs/mobx-state-tree/issues/989#issuecomment-416390236,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABvGhAajIeyWImy3uvjFFA1CavsoCvfiks5uVHM_gaJpZM4WLRCG
.

maybe you are using Arrow functions? The 'this' trick doesn't work with those

or alternatively this

const Example = types.model("Example", { prop: types.string }).views(self => ({
        get upperProp(): string {
            return self.prop.toUpperCase()
        },
        get twiceUpperProp(): string {
            return this.upperProp + this.upperProp
        }
    }))

@xaviergonz
When using flow to write async action, this is implicit, does this means the only solution is writing multiple .action() ?

Can I call action3 in action2?

mstModel
.actions((self) => ({
    action1: flow(function*() {
      console.log('this is action1');
    }),
  }))
  .actions((self) => ({
    action2: flow(function*() {
      self.action1(); // right
      self.action3(); // wrong
      console.log('this is action2');
    }),
  }))
  .actions((self) => ({
    action3: flow(function*() {
      console.log('this is action3');
    }),
  }))

Can I call action3 in action2?

mstModel
.actions((self) => ({
    action1: flow(function*() {
      console.log('this is action1');
    }),
  }))
  .actions((self) => ({
    action2: flow(function*() {
      self.action1(); // right
      self.action3(); // wrong
      console.log('this is action2');
    }),
  }))
  .actions((self) => ({
    action3: flow(function*() {
      console.log('this is action3');
    }),
  }))

@xaviergonz
Maybe this is the best solution ?

.actions((self) => {
    const actions  = {
      action1: flow(function*() {
        console.log('this is action1');
      }),
      action2: flow(function*() {
        actions.action1(); // right
        actions.action3(); // right
        console.log('this is action2');
      }),
      action3: flow(function*() {
        console.log('this is action3');
      }),
    };
    return actions;
  })

that's a way to solve it yes, however be warned that inter calls between actions won't be actions themselves IIRC

What does that mean? Please tell me more details. Thank You.

Using getRoot and find current model like reflection

.actions(self => ({
    a: () => {
      const root = getRoot(self)
      root.currentModel.b()
    },
    b: () => {
      console.log('called b function')
   }
Was this page helpful?
0 / 5 - 0 ratings

Related issues

elado picture elado  Â·  4Comments

donatoaz picture donatoaz  Â·  3Comments

misantronic picture misantronic  Â·  3Comments

lishine picture lishine  Â·  4Comments

mreed picture mreed  Â·  3Comments