mobxjs/mobx-state-tree

TypeScript does not recognize overriding mandatory property with optional one in sub-model

andreykurilin opened this issue · 6 comments

Minimal reproduction code

import { types } from 'mobx-state-tree';

export const ErrorStore = types
  .model('ErrorStore', {
    value: '',
  })
  .actions((self) => ({
    set(value: string) {
      self.value = value;
    },
    reset() {
      self.value = '';
    },
  }))
  .views((self) => ({
    get formattedValue() {
      return self.value;
    },
  }));

export const NoDisplayError = ErrorStore.named('NoDisplayError').views(() => ({
  get formattedValue() {
    return undefined;
  },
}));


const InputStore = types.model("InputStore", {
    value: "",
    // developer should explicitly specify whether validation errors should be displayed or not
    error: types.union(ErrorStore, NoDisplayError)
});


const PasswordInputStore = InputStore.named("PasswordInputStore").props({
    // we have strict requirements for password, so validation errors should be displayed by default
    error: types.optional(types.union(ErrorStore, NoDisplayError), () => ErrorStore.create({}))
});


const passwordS = PasswordInputStore.create({});

Describe the expected behavior

Initialization of PasswordInputStore should not require specifying optional error property.

Describe the observed behavior

TypeScript fails as error property is not provided.

      TS2345: Argument of type '{}' is not assignable to parameter of type 'ModelCreationType<{ value: string | undefined; error: EmptyObject & Partial<{ value: string | undefined; }>; }>'.
  Property 'error' is missing in type '{}' but required in type '{ error: EmptyObject & Partial<{ value: string | undefined; }>; }'.

Thanks for the issue report @andreykurilin, and I'm sorry for the inconvenience!

I see @thegedge has assigned this to himself, so I expect good things here.

I put together a CodeSandbox with the repro code in case that helps demonstrate the issue for others: https://codesandbox.io/p/sandbox/g3w8ft?file=%2Fsrc%2Findex.ts%3A8%2C15

Sorry for the delay, but it's been really busy for me lately.

Thought I'd explore what's happening here:

CleanShot 2024-11-09 at 10 16 51@2x

Makes sense that if you have PropsA & PropsB with one having a required prop and the other not, the required prop wins. Since the way props works in MST is to "clobber" properties, I think the fix will be simple: Omit<PropsA, keyof PropsB> & PropsB.

[EDIT]
I suspect we may want to do this for anything else that "clobbers"; like views, actions, extend, and so on.