Subscribe on changes!

Type `() => boolean` is not assignable type `boolean` when using `withDefault` with a factory function

avatar
Feb 9th 2023

Vue version

3.2.47

Link to minimal reproduction

.

Steps to reproduce

Type '() => boolean' is not assignable to type 'boolean | ((props: Readonly<Omit<Props, "expanded" | "hwAcceleration"> & { expanded: boolean; hwAcceleration: boolean; }>) => false) | ((props: Readonly<Omit<Props, "expanded" | "hwAcceleration"> & { ...; }>) => true) | undefined'. Type '() => boolean' is not assignable to type '(props: Readonly<Omit<Props, "expanded" | "hwAcceleration"> & { expanded: boolean; hwAcceleration: boolean; }>) => false'.

Not sure why there is a type error on hwAcceleration but not duration if it's invalid.

export interface Props {
    expanded: boolean;
    duration?: number;
    hwAcceleration?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
    expanded: false,
    duration: () => inject<PluginOptions>(PLUGIN_KEY)?.duration || 300,
    hwAcceleration: () => inject<PluginOptions>(PLUGIN_KEY)?.hwAcceleration || false,
});

Changing the interface to this solve the type error, but it seems wrong. The type of the value is still boolean, the fact that Vue need a factory function in withDefaults doesn't change the nature of the value, is this a bug with withDefaults ?

export interface Props {
    expanded: boolean;
    duration?: () => number;
    hwAcceleration?: () => boolean;
}

What is expected?

It shouldn't be required from the Vue documentation. Reference: https://vuejs.org/api/sfc-script-setup.html#default-props-values-when-using-type-declaration.

export interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})

What is actually happening?

Type '() => boolean' is not assignable to type 'boolean | ((props: Readonly<Omit<Props, "expanded" | "hwAcceleration"> & { expanded: boolean; hwAcceleration: boolean; }>) => false) | ((props: Readonly<Omit<Props, "expanded" | "hwAcceleration"> & { ...; }>) => true) | undefined'. Type '() => boolean' is not assignable to type '(props: Readonly<Omit<Props, "expanded" | "hwAcceleration"> & { expanded: boolean; hwAcceleration: boolean; }>) => false'.

System Info

No response

Any additional comments?

No response

avatar
Feb 9th 2023

If I add a // @ts-ignore and check the built code. I see this, which seems perfectly fine.

{
  expanded: { type: Boolean, required: !0, default: !1 },
  duration: {
    type: Number,
    required: !1,
    default: () => {
      var e;
      return ((e = i(s)) == null ? void 0 : e.duration) || 300;
    },
  },
  hwAcceleration: {
    type: Boolean,
    required: !1,
    default: () => {
      var e;
      return ((e = i(s)) == null ? void 0 : e.hwAcceleration) || !1;
    },
  },
}
avatar
May 1st 2023

@LinusBorg @yyx990803 How are pending PR reviewed/managed ? Any chance that this will get merged with 3.3 ?

avatar
Jun 26th 2023

@LinusBorg @yyx990803 Sorry to ping again, but any idea when this could get merged ? I would be sad to have to wait another 2 years like we did between 3.2 and 3.3

avatar
Aug 22nd 2023

EDIT: I just realized that the withDefaults macro is documents to remove undefined from the defaulted props; however, in my use case the function used for the default value can return a value OR undefined, depending on circumstances, where undefined is a perfectly valid value meaning "unused". Perhaps withDefaults is just not appropriate for this use case? Original comment follows:

I don't know if this is related, but if a prop is optional, returning undefined from a function also triggers a type error, even though doing the same in the options API is fine (and the only way to positively assign undefined as the default value).

e.g., given:

interface Props {
   opt?: string
}

Then:

const props: Props = defineProps({
  opt: String,
  required: false,
  default: () => undefined
})

works fine, but:

const props = withDefaults(defineProps<Props>(), {
  opt: () => undefined
})

causes a type error:

Type '() => undefined' is not assignable to type 'InferDefault<Props, string | undefined> | undefined'.
  Type '() => undefined' is not assignable to type '(props: Props) => string'.ts(2322)
avatar
Aug 23rd 2023

@nborko Well, in your first exemple the opt props is optional opt?: string. But in your second the props is not (you ommited the ?). You code should be this:

const props = withDefaults(defineProps<Props>(), {
  opt?: () => undefined
})
avatar
Oct 25th 2023

Closing this issue since it's working on the latest version, playground

image