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

Model instance maybe fields becoming TypeScript optional fields when included in a types.union

Open fruitraccoon opened this issue 5 years ago • 2 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
  • [x] Fork this code sandbox or another minimal reproduction.

Sandbox link or minimal reproduction code https://codesandbox.io/s/wispy-meadow-z49x9

I've pinned the MST and Mobx versions in the reproduction, so that the issue can be reproduced if new versions affect the behaviour in any way (good or bad!).

Describe the expected behavior That the TypeScript interface derived from Instance<typeof Model> would always "fit" an instance of that model.

Describe the observed behavior When a child model has an a "maybe" field, and is then used within a union type, attempting to pass an instance of this model from the union field to a React prop typed as Instance<typeof Model> fails with "Property '...' is optional in type ... but required in type 'IModel'"

It's probably easiest to view the linked reproduction code.

I think what's happening is that when the model type is coming from within the union, "maybe" fields are becoming optional fields - but they are still mandatory fields on the derived interface. I can see that typescript reports the model type differently when it's come from the union vs. when it has not.

I realise this is a pretty specific use case, but I thought it worth raising. If there's any issue with the reproduction or more information is required, please let me know!

fruitraccoon avatar May 18 '20 02:05 fruitraccoon

Hallo, I have the same issue.

Property is optional in type but required in type ... For example - property in model is name: types.maybe(types.string) type and ts object which I'd like to assign to this model is name? string which cause incompatibility error. I have tried to use types.optional but it doesn't help.

At root level of model I tried this type:

export type Complete<T> = {
  [P in keyof Required<T>]: Pick<T, P> extends Required<Pick<T, P>>
    ? T[P]
    : T[P] | undefined;
};

and being applied to root model it helps to transform typescript optional to types.maybe, but suddenly it does not work for inner models and problem still exists.

    "mobx-state-tree": "~4.0.2",
    "typescript": "~4.1.2",

alex-shamshurin avatar Dec 07 '20 10:12 alex-shamshurin

Hey @fruitraccoon - sorry it's been so long since anyone got back to you here. Not sure if this is still a relevant issue. I have two thoughts:

  1. In the short term, you can provide your itemWithIssueModel variable with an explicit type annotation, like this:
const itemWithIssueModel = store.itemWithIssue === "anotherValue" ? null : store.itemWithIssue as IModel;

I forked your code sandbox here with that change: https://codesandbox.io/s/pensive-goodall-rzz2p6?file=/src/index.tsx

  1. In the long term, I think this is another issue with our overall TypeScript system, which we know is a longstanding and frustrating problem. I'm going to keep this issue open and tag it as such. We are trying to revamp MST a little bit, and TS is one of the big ticket items for us.

If you're interested in helping, we'd love to chat. If not, we'll still get to work on this in the coming months. Appreciate all your time reproducing and documenting the problem.

coolsoftwaretyler avatar Jun 30 '23 16:06 coolsoftwaretyler

Hey folks, we finally got around to this. Huge thanks to @thegedge for resolving it in https://github.com/mobxjs/mobx-state-tree/pull/2151.

This may end up being a breaking change, so keep an eye out for a preview release and RFC around versioning.

Thanks for hanging in there with us on this one.

coolsoftwaretyler avatar Mar 01 '24 15:03 coolsoftwaretyler