Subscribe on changes!

defineModel does not emit when using default value

avatar
Jan 4th 2024

Vue version

3.4.4

Link to minimal reproduction

https://play.vuejs.org/#eNqNU8lu2zAQ/RWCKOAUiEUUzsmVHbdFDi3QBW2PPESRRg4TcQE5VBwI+vcOJdtV3ATNiZz9vVk6/sG5rI3AlzwPpVcOWQCMbi2N0s56ZB3zULOe1d5qNiPX2dH0yWq312ciCSnT7L00pTUBmX5MkasUf/ZWmlyMBSg1CQjaNQUCSYzlQ6Z2rm0FzUryIVJyJkarW7dFE2HJum7MeXnJrvM3HT46sPVYp19f930uEnCKuImI1rBN2ajy/pBwFU0FtTJQSb72QDxzMTpSUC6OiPg5HwnOdeGyu2ANdadLeeXeECQnMEmTdEQ6yZLfIrqwFKKsDIURFdX6zAAK47TYkJvw0aDSMK+s3iyyi+xCVCrgVJ1B0PMbbx8CeEoi+fmkjCBlC37ugZh48K8texI2LX1i+qd8qt5L01NTMNBca7U9aUlJs1MN+O8OFc39SWuKprEPXwYd+ghHLuUtlPfP6O/CbuT0I82HkE34Y+G3gKP56tc32NH/aKTNic1+DC8Yf0KwTUwYR7ePtA4Ee+I3oP08TFiZ7e9wtUMw4UAqAR26MfgP80hr+xL1v3AX2WLSxcOdPHNx+7NJR0BnM+7q1ySddSQUscEle9f/55SUcRGnt5ReWvgnKy4N7/8AM6ZcSg==

Steps to reproduce

Case A: open the repro link Case B:

  1. update the value in the input
  2. click the "reset" button

What is expected?

In both cases: the value in the input should be the same as the value below, and both should use the component's default value.

What is actually happening?

In both cases: the input uses the component's default value, but the value below is undefined.

System Info

No response

Any additional comments?

No response

avatar
Jan 4th 2024

This is not a bug. The default value of defineModel is equivalent to the default value of prop. When emit is not triggered, the value will not be synchronized to the parent component.

avatar
Jan 4th 2024

From the docs:

This macro can be used to declare a two-way binding prop that can be consumed via v-model from the parent component.

I would expect this to mean that the model value in the child component stays in sync with the ref value in the parent.

Under the hood, this macro declares a model prop and a corresponding value update event.

The purpose of the macro is not only the creation of the prop, but also the creation of the update event and the ref in the child component. If the macro is responsible for assigning a default value to the ref, then it should also be responsible for emitting the default value to the parent.

avatar
Jan 4th 2024

I would expect this to mean that the model value in the child component stays in sync with the ref value in the parent.

Well it does, it you don't define a default value. Defining a default value for a prop means "use this value if the parent doesn't provide a value", which is technically indistinguishable from "provides undefined as the value"

avatar
Jan 5th 2024

If we don't want to treat this as a bug, can we reopen it as a feature request? I don't think this default behavior is useful or makes sense, but if we want to keep it, it would be nice to have an option to make the model automatically emit when using its default value--the current workaround is to run

watchEffect(() => model.value = model.value)

which has 3 issues:

  1. it looks dumb
  2. it causes double updates for non-default values
  3. it doesn't work if ref.value = undefined is run when ref.value already has the default value Example: https://play.vuejs.org/#eNqNU8lu2zAQ/RWCKGAHicQGzsmVHbeFDy3QBV1uOkSRRjYTiSS42A4E/XuHouTKbtPmJM2b5c0M5zX0rVLxzgGd08TkmitLDFinlqngtZLakoZoKElLSi1rMsHQydH1Xtaqx2PmDV9p8iYVuRTGkvrJZy58/vT6YkCdKjILpsdfI56wwIycaFioVYURaBGSdBS7qJYFVIuUdiVTSlahyrzDo11WOUBvX/ryMqUspKtl55uTpgnd3N6Su+RVY58UyDJ02C7v2jZhfmTMuHfWSkFWecXzx4Fx4UQBJRdQpHSpATeUsBA40PwM1J6o72KombDjRPSKhs1FdabiByMFrr3xFdLeYVKKJTziMdymt1O6tVaZOWN5ITANR+Y7HQuwTKiarTCMaScsryEqZL2axTfxDSu4sWM4BlNH91ruDWgsktKrEQ1DcAc60oCDatAvpT1LG1Ofuf6g9+xtKlpcijV4GiXfnK0kx7fnFegvynI8nZPVZFUl9x87zGoHx1nyLeSPf8EfzCHM9NU/H3Y2mt9megM2uNffP8MB/49OvDBX9c/wjPMbGFk532MIe4fXgm2P4rpuP3QvzMXmh1kfLAgzDOUb7bbRxXfv4c/+udF/tzuLZ6MtDgL8l5T3mc2367KE3J5IGjV7VK2XFKozHPwnb00bNDJX2Tm5blGwoyrT6QVZLENS3GkNU0fWf+TNhXJ2rG//RY2dyCYVtP0FGfGZNQ== Notice how clicking "reset" twice in a row causes the value below the input to become undefined--inside the component, there's no way to tell the difference between the prop being undefined vs. having the same value as the default, so it's impossible to emit an update.

Or if we feel that implementing a fix for this would be too complex (since it may involve changing how props work or creating a new system for default model values), can we at least add a warning in the documentation that default values can cause the component/parent ref values to desync?

avatar
Jan 5th 2024
const model = defineModel()

watchEffect(() => {
  if (!model.value) model.value = 1
})
avatar
Jan 6th 2024

Yeah, that's pretty much my conclusion as well--the defaults system that comes from props is not ideal for models because of these weird edge cases; it's much simpler to just implement custom defaults like this instead. If this is the case, then it may be worth updating the documentation accordingly.

avatar
Jan 6th 2024

The docs team would be thankful for an issue about this or even a PR: https://github.com/vuejs/docs

avatar
Jan 7th 2024

Thanks for that pointer; there's also a part of the defineModel documentation in the source code that reads:

 * The the returned ref behaves differently depending on whether the parent
 * provided the corresponding v-model props or not:
 * - If yes, the returned ref's value will always be in sync with the parent
 *   prop.
 * - If not, the returned ref will behave like a normal local ref.

If default is used, it's not technically correct to say "will always be in sync," so that line should probably be updated somehow (but as I mentioned in the issue in the docs repo, I'm not sure whether it would be better to highlight the odd default behavior or simply to discourage using default/pretend it doesn't exist).