defineModel does not emit when using default value
Vue version
3.4.4
Link to minimal reproduction
Steps to reproduce
Case A: open the repro link Case B:
- update the value in the input
- 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
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.
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.
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"
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:
- it looks dumb
- it causes double updates for non-default values
- 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?
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.
The docs team would be thankful for an issue about this or even a PR: https://github.com/vuejs/docs
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).