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

Typescript: Type of Action Parameter in Union

Open chriszwickerocteris opened this issue 6 years ago • 3 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 (checked with 3.14.1)
  • [x] Fork this code sandbox or another minimal reproduction.

Also, I've asked this as a question on stackoverflow.

Sandbox link or minimal reproduction code

const Foo = types.model({
  id: types.identifier,
  payload: types.maybe(types.string),
}).actions((self) => ({
  updatePayload(payload?: string) {
    self.payload = payload;
  },
}));

const Bar = types.model({
  id: types.identifier,
  payload: types.array(types.array(types.boolean)),
}).actions((self) => ({
  updatePayload(payload: boolean[][]) {
    applySnapshot(self.payload, payload);
  },
}));

const Item = types.union(Foo, Bar);
const Bucket = types.model({
  items: types.map(Item),
}).actions((self) => ({
  updateItemPayload(id: string, payload?: string | boolean[][]) {
    const item = self.items.get(id);
    item && item.updatePayload(payload);
  },
}));

Describe the expected behavior Item#updatePayload should (I believe) expect a parameter of type I extends SnapshotOrInstance<typeof Foo> ? string | undefined : boolean[][]

Describe the observed behavior Item#updatePayload expects a parameter of type string & boolean[][], which of course is impossible.

chriszwickerocteris avatar Aug 20 '19 20:08 chriszwickerocteris

Hey @chriszwickerocteris - I'm sorry no one ever got back to you here. We are trying to revamp the MST repository, and TypeScript issues are definitely on our radar.

I'm going to tag this as such. If this issue is still relevant to you and you're interested in helping, let me know. But I totally understand if it's just not a priority for ya anymore.

Thanks!

coolsoftwaretyler avatar Jun 30 '23 21:06 coolsoftwaretyler

Hi @coolsoftwaretyler - we finally moved away from MST, so at least for now, this isn't an issue for me any longer. Thanks anyway ;-)

chriszwickerocteris avatar Aug 22 '23 08:08 chriszwickerocteris

Thanks for the follow up @chriszwickerocteris, sorry it didn't work out for ya.

We'll leave the issue open as we work to revamp everything

coolsoftwaretyler avatar Aug 22 '23 16:08 coolsoftwaretyler

Since I've been picking up some of the TS work, I can pitch in.

Both MST and TS are doing the right thing. In updateItemPayload, item.updatePayload is going to be of this type:

type F = ((payload: boolean[][]) => void) | ((payload: string) => void)

It doesn't know which of those two the function is, so what happens if we give F a string and the underlying instance is actually the function that takes a boolean[][]? That's why you're seeing boolean[][] & string, because if the payload you're providing is both possible types at the same time, it'll work for all members of the union. A simplified version of this in the TS playground:

typescript playground screenshot showing a similar error

Given your example, you'd unfortunately have to narrow both the argument and the function to make TS happy, like this:

if (Foo.is(item) && typeof payload == "string") {
  item.updatePayload(payload);
} else if (Bar.is(item) && Array.isArray(payload)) {
  item.updatePayload(payload);
} else {
  const type = getType(item);
  throw new Error(`Item type ${type.name} can't process payloads of type ${typeof payload}`);
}

Hope this helps! (and given this, I think we can close the issue?)

thegedge avatar Feb 24 '24 22:02 thegedge

Thanks @thegedge - that's very helpful. I'm going to convert this to a discussion, which will close the issue. Then I'll mark your answer as "the" answer.

Really appreciate your time, and your TS expertise.

coolsoftwaretyler avatar Feb 24 '24 23:02 coolsoftwaretyler