mobx-state-tree
mobx-state-tree copied to clipboard
Typescript: Type of Action Parameter in Union
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 oranother 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.
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!
Hi @coolsoftwaretyler - we finally moved away from MST, so at least for now, this isn't an issue for me any longer. Thanks anyway ;-)
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
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:
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?)
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.