vuejs / core

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
https://vuejs.org/
MIT License
47.65k stars 8.33k forks source link

v-model not working on checkbox for nested ref #8526

Closed panstromek closed 1 year ago

panstromek commented 1 year ago

Vue version

vue@3.3.4

Link to minimal reproduction

https://play.vuejs.org/#eNplUUtugzAQvcrIG4hCknZLk0hVj9DuShfUGcCKsS3b0EaIu3cMriDKzvN7Pw/s1Zh93yHL2dFja2Tp8VwogONF9NNjfkK/q7Q9Fcyh90LVIBTEpysY5Fe8LcM9VQWL1wFAKNN5gmj1BeVqry8lHfubQWryBvn1W/9S55/4sIh4i1PgTalqvIAXLbochiE2PkI9jpP2eHc8rCxR6bgVxgfdnQFJV8TqST5NRWu09TCAxSqDn9LzBkaorG6hYJRPwV4CBNfKEQBpl/gekzjBEEjJcw5JdLZ7TrLQJIN5gEyrUjrcUI8EziBr2YQRlp42gWMiT+84QlAZpLyzFpXPwFjshe7cBk7nmT1gaol7qes0idBJBo8X0/JCvd2Sr3Hijd7ip5KkzzsNX1OEhzlDSoyNf6flx1k=

Steps to reproduce

Click the checkbox few times

What is expected?

Counter should be incremented when clicking on the checkbox (also a log in the console should be shown)

What is actually happening?

Counter is not incremented and no log appear in the console

System Info

No response

Any additional comments?

No response

panstromek commented 1 year ago

From some further testing, it looks like v-model will replace the whole Ref with a primitive boolean value after you press the checkbox. It seems that the root object is assumed to be reactive but it isn't in this case.

I feel like I have an intuition for why this happens, but it's a bit unfortunate footgun, combined with the recommendation to return non-reactive objects from composables (my original intent was to create some useSetting composable here).

baiwusanyu-c commented 1 year ago
<template>
  <div>
    <div v-for="setting in settings" :key="setting.key">
       <input v-model="setting.val.value" type="checkbox" >
    </div>
    Checkbox changed times: {{changedTimes}}
  </div>
</template>

<script setup lang="ts">
import { ref, watch } from "vue";
const singleSetting = {
  key: 'setting-1',
  val: ref(false),
}
const changedTimes = ref(0)

watch(singleSetting.val, (current, previous) => {
  console.log('changed', current, previous)
  changedTimes.value++;
})

const settings = [singleSetting]

</script>
baiwusanyu-c commented 1 year ago

I don't think it's a bug. In your usage method, in the template, ref will not be unwrapped automatically, you need to access .value by yourself (https://vuejs.org/guide/essentials/reactivity-fundamentals.html#ref-unwrapping-in-templates)

baiwusanyu-c commented 1 year ago

In your demo there are typo mistakes and ref syntax errors

const changedTimes = ref(0)

watch(singleSetting.val, (current, previous) => {
  console.log('changed', current, previous)
  changedTime++;
})
panstromek commented 1 year ago

I don't think it's a bug. In your usage method, in the template, ref will not be unwrapped automatically, you need to access .value by yourself (https://vuejs.org/guide/essentials/reactivity-fundamentals.html#ref-unwrapping-in-templates)

I tried that and it generates an error. But doing it in the reproduction didn't, I'll have to investigate in the original code. Also, this link is dead, the chapter is not present.

In your demo there are typo mistakes and ref syntax errors

const changedTimes = ref(0)

watch(singleSetting.val, (current, previous) => {
  console.log('changed', current, previous)
  changedTime++;
})

oh, sorry. I messed it up when reducing the problem.

panstromek commented 1 year ago

Looks like the problem was leftower state from hot reload (changing to .value access) tried to set value on raw boolean, that was left from previous version with ref that was assumed to be unwrapped. If I refresh the page, it works correctly (when accessing .value).

Thanks for looking at this.