core icon indicating copy to clipboard operation
core copied to clipboard

defineModel value de-synchronizes between parent and child if @update handler is not provided

Open CasualSuperman opened this issue 1 year ago • 6 comments

Vue version

3.4.15

Link to minimal reproduction

https://play.vuejs.org/#eNrNVc1u2zAMfhXWl7RoE6PodsncYGvRwwZsK9pil3kYVItJ1MqSIclZgyB79lFynDqJ4xzXmyRS5Ed+/FlEn4piMCsxGkaJzYwoHFh0ZTFKlcgLbRwswOAYljA2OoceqfY+rGXXOi9WgkHsL96Ul6cq08o6+sq4VnJ+a3QBl97SsTMlnpDKpsJXzVFuaSRxhYiw0MVhXkjmkG4AiS2YGt0yg8qBzoVzQk2g8F7oRRiUc/rtdYL2o4G4OnExCwc6ZuSyNjFjssQhlIrjWCjkSRykK831dzqHkNe3x9I5rYALyx4l8tGDnkwkQmU1iStxmxmPDTLJrL1MI6HIv+D9HK1lE0yjGhZTHK6nQnKgOyjtQKi+navsqBldXAfViHN12skVpWgmuE+Wz3qVsRA8JJiDdXOJBCjTUpuhQU5Qjq/KCdxh4Ps+Q8WM0CdJjPmoNcN1lmHImWP9ml8y2qwFsrufhcWiqbpcHiCDcO7xscPTx0yK7HlLj6ruaAtbJ43/n7oGa95NWVCqEaZ0lmia5jt5CC23ScRh5X1MBflBqmb93Ou1MbZycJiyelKsOauhvY3eS+LGoKJraCk6DrYcwcL/qhqNph6neQd+jtDX+ZDcKQwvBeOe8r7EsRvCeTal12WqyOvPTap6fmr2fsGIbHLcFo6ZtF76F7ZxnLUaq/Xbra1c7RqrglpHIZSkYVohpqSFTERnkbM0+8diMniyWtHiCZ/80MkLQQX8vXCCdkMaUX1VjKURk1L/+RLevHMCXb1nU8yeW96f7It/S6NbgxbNjHhdyxwzE3SV+Ob+G77QeS2k+iwlaXcI79BqWXqMldoVbQ2C3dALaD+HFUncPdibF4fK1kF5oF5zGfTTiFamb459ob/CvRhchH+UT8pivW5bNvfmaqVeqbZaaJTj104ixLAgESsl1ZaHtTy0druHBDVP1TE/tsfDxmRozro9Td7s79fWDsYbnb2v1zpq9hTI+WlXjxArXR3UYWDlYrexN8v/9wyNLwYi7mLwbnD+Plr+A5zcQls=

Steps to reproduce

Provide a prop value but no @update handler for a v-model prop on a child component using defineModel.

To reproduce in the example, click the middle "Toggle Child" button.

What is expected?

Props are not typically considered writable, and defineModel emulates a prop/$emit pair, so writes to the value should not have a (direct) effect. Since the parent component has provided a value, that value is the one that should be used. This demonstrates what I think is the expected behavior, since this is how v-models are documented to work under the hood. Omitting the update event handler should tell the child component that the parent does not care if the child wants to update the value, and the event/update should be ignored.

What is actually happening?

After initialization, the value behaves like a local ref, and ignores the value provided by the parent, unless it changes.

System Info

System:
    OS: Linux 6.6 Arch Linux
    CPU: (8) x64 Intel(R) Core(TM) i7-10610U CPU @ 1.80GHz
    Memory: 2.11 GB / 30.98 GB
    Container: Yes
    Shell: 5.2.26 - /bin/bash
  Binaries:
    Node: 18.14.2 - ~/.nvm/versions/node/v18.14.2/bin/node
    Yarn: 4.1.0 - /usr/bin/yarn
    npm: 9.5.0 - ~/.nvm/versions/node/v18.14.2/bin/npm
    pnpm: 8.6.0 - /usr/bin/pnpm
  npmPackages:
    vue: ^3.4.15 => 3.4.15 

Any additional comments?

No response

CasualSuperman avatar Feb 17 '24 17:02 CasualSuperman

Some context here https://github.com/vuejs/rfcs/discussions/503#discussioncomment-7828483

sqal avatar Feb 17 '24 23:02 sqal

This is actually designed this way, but missing in the docs. We'll make sure to amend the docs accordingly. I'll leave this open until we have done that.

LinusBorg avatar Feb 20 '24 15:02 LinusBorg

How can we get around this functionality when we cant directly use v-model?

Example (not the author of this reddit post): https://www.reddit.com/r/vuejs/comments/1clppby/tsx_error_when_registering_event_listener_for_an.

I'm having the same issue where I'm using Cypress component testing, but the emitted function provided by @vue/test-utils isn't catching the emitted value.

jack-allocate avatar May 09 '24 17:05 jack-allocate

This seems really unintuitive to me. As was mentioned in the other thread I too would like to be able to define this behavior on a case by case basis instead of attaching an empty event listener just to force a child component to accept a certain value.

If I have child component with const model = defineModel('open') and set :open="true" in the parent it seems very strange to me that it is allowed to bypass that.

The previous design philosophy always seemed to be that any value that is or isn't sent as a prop should still only mutate in a single place. Has this now changed?

gburning avatar Jul 23 '24 11:07 gburning

I am unsure, if I understand the use-case of defineModel correctly?

E.g., we are building a component library and defineModel creates typings which allow setting the property without v-model and or event handler: <Comp :modelProp="true" />. But with the current implementation of defineModel it will always behave unexpected. The component Comp will not actually use the modelProp property like a static value.

If the behavior is intended, the component typing should only allow v-model bindings<Comp v-model:modelProp="someRef" /> and disallow property bindings without event handler <Comp :modelProp="true" />.

But still from user perspective the API here seems weird, when defineModel behaves differently from defining props and emit manually:

const props = defineProps<{ modelValue: boolean }>());

const emit = defineEmits<{
  "update:modelValue": [value: boolean];
}>();

JoCa96 avatar Jul 31 '24 13:07 JoCa96

This is actually designed this way, but missing in the docs. We'll make sure to amend the docs accordingly. I'll leave this open until we have done that.

I would recommend providing more documentation on the whole topic of defineModel. At least there should also be a warning that mutations on object fields or array entries will not be fire an 'update:modelValue' event, just re-assignments.

muelbr avatar Oct 21 '25 16:10 muelbr