Skip to content

BUG | toRefs | When nested, the inner and outer layer fields cannot be updated simultaneously. #4771

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
7 tasks done
eric-gitta-moore opened this issue May 23, 2025 · 2 comments

Comments

@eric-gitta-moore
Copy link

Describe the bug

TL;DR;

<script setup>
import { watch, toRefs as _toRefs, defineModel } from "vue";
import { toRefs } from '@vueuse/core'

const test = defineModel('test')

const { p, name } = toRefs(test)
// const { p, name } = toRefs(test, { replaceRef: false }) // It will work
const { c } = toRefs(p)

setTimeout(() => {
  name.value = 'check666'
  c.value = 'testc' // It won't work!
  setTimeout(() => { c.value = 'testc' }) // It will work
}, 1000)
</script>

My objective: To nest the v-model structure while maintaining reactivity


First version: Using vue's toRefs
Suppose there is a drawer component. Every time it is opened, a new copy of the data from Pinia is fetched. The second opening will cause toRefs to fail.

The problem lies in that, fundamentally, vue.toRefs does not trigger vue#useModel#CustomRefImpl.set
const { key } = vue.toRefs(modelValue.value), key.value = 123 actually modifies the props
When the props are completely replaced, the sub-object generated by the child component and the props passed from the parent lose their relationship

Normal working condition:play

Failure scenarios:play


Second version: Using vueuse.toRefs, the modelValue will remain reactive.

The fundamental reason for its normal operation lies in the fact that in any case, the set operation will enter modelValue.value = xxx

e.g.

const { val, val2 } = vueuse.toRefs(modelValue)
val.value = 123 // work
val2.value = 456 // work

However, my situation is rather special. I need to restructure. Below is my situation.

<template>
  <Comp v-model='role' />
</template>
<script>
const role = [
  name: 'thisIsName',
  extra: {
    uuidOnlyInFe: 'xxx'
  }
]
</script>

So I had to, once again, deconstruct. But this also brought about problems.

// modelValue expect `[ name: 'thisIsName', extra: { uuidOnlyInFe: 'xxx' } ]`
const { name, extra } = vueuse.toRefs(modelValue)
const { uuidOnlyInFe } = vueuse.toRefs(extra)
name.value = 123 // not work
uuidOnlyInFe.value = 'xxx' // work

Reproduction: play

After repeated debugging, it was found that if replaceRef: true (the default value) is used to use vueuse.toRefs, it will result in two customRef.set operations.
However, the problem lies in the second customRef.set, which uses the data before name.value = 123 to construct newObject, thereby overwriting the modification made in the previous line.


Version 3: This is a temporary solution vueuse.toRefs(modelValue, { replaceRef: false })
However, what I'm unsure about is whether replaceRef: false might cause any other bugs

Reproduction

https://play.vuejs.org/#eNqNVdtu2zgQ/ZVZvdgGbClBtnnQ2kG7aRZIsU2LJrsvVdGo0ihhLJEESfkCw//eQ8py3DZpCwQKOTOcOXM73kSvtI4XLUdpNLWFEdqRZdfqs0yKRivjaENFa51qPnA1JuM/y9wV92NS8q1qpeOStlQZ1dAAfgb7d+eq0Z08i+LE33ycLMpkJgslraOiVpJfM2uaEdPsjN5cv7uKdW4sD8PROiPknajWQx6N+levL/559d+/N5+9BR5+3JBOPciUBkIKdz6g7Zhk3vBOcIUjZJ/6947xmflMhnsAw0OnPlQmk4QuK3L3whL+ShiOcWNqLZOq6NYpFMQOG1Vy/X9etxwv/Hd0S0KG3H22tBR1TV+4UA1DzlXFhRMLjjOJKt+IhlXrhsORT36TSULQ61ZrhRiIZfDGUk6lyZdsCE40YEhHXhXTxYLNmhyckHDeUmmWXI7xQvIS5nrtkXrQZe5ybwGZQL9CV96jODmAhIJ04FGWZ0qSSRT1xdFRKM006SYFM4KL40bXuWPciKalWMBHbu0si5Ym15pNFgXVTrmYVMpAORR4OCZRrka+Yh5EFlE65zWUkOJi3bpm3EphEWGdVjWv/tp7I/823Wz8P9pup2HeFpPQkNS7w0v/7yP0n/BqGmawh5IAS8Afbr2YCNOHCmOgNt2gbLd7o6Sz6p9Ok4PMcQ1ocYx3aYeGUo+dOvCQ+MOkFMZPgpIpelK3jQyqvBZ3cuILYyEHDjZB/oAFxBZMMMAuoNvrgA7dCJGjceQsLCpxFz9YJbHRAUEW+cERNZt32ke0WYT0upyyKK9rtXwTZM60PO7lxT0X8yfkDxadSXF4b9iyWWCh9zqXmztGF7364vqKVzjvlehLW8P6J8oPbFELj7Ez+7uVJWAf2AW0l4FfQAw39mKFetg+KQ/UW26DfRZhAX3Ln0v9Ee5J/Gd4h3qiiv3y/owSdxRYcoWtfutHrifBEDaL0Bss896+Y4unifIH7UuoQTNJoYw3+464DmIOB142CEvZ2YAMO/KDs9nO79AbwQZ4fmE0hsow5rVALyrMbF6DibYjz0uXriOzpTLzx2jFoQsdgDxJbCHcnmQGYbpOT0+RHXnnqHeg0MIrPRI9CPJvgxIVjy7OUQFRgc0GvaGSA/818z8o+AcrpnQb8pzNDoPe7qL+iPQwgIdR4IfjiezBhcdHv0mGPYl9UaZkk9KxXhHGXJSkhZw/cqM+0zPPOhqU03UnXLs2bXvy6QqZ0lRI3bqe7uDei0GZyfMUhcnuBm7S5Po7hugU367H4RR2+3jvnLZpkrRSz+9isEpyaPPy+CQ+jo8SIUtexc0DvO33trez96DX8lfeOqsn/B0s6Wf8+PnFRw4n8Yv4+CTafgVmru2y

System Info

vue: v3.5.13
vueuse: 13.1.0

Used Package Manager

npm

Validations

@eric-gitta-moore
Copy link
Author

eric-gitta-moore commented May 23, 2025

One more thing to add is that this issue is also related to Vue itself to some extent.

Since in versions 3.4+ of Vue, defineModel is actually a wrapper for useModel

Inside useModel, a localValue is maintained, and it asynchronous synchronizes props[camelizedName] using watchSyncEffect. This results in the issue where values cannot be read immediately after consecutive assignments.

However, there is one thing to note: if the parent component does not pass the v-model, it will still function properly.
This is related to the implementation of useModel. ref: no v-model, local update

e.g. playground

<script setup>
import { defineEmits, defineProps } from 'vue';

const emits = defineEmits(['ok'])
const props = defineProps({
  msg: {
    type: String,
    default: 'Hello World!'
  }
})

setTimeout(() => {
  console.log(props.msg) // => Hello World!
  emits('ok', '1')
  console.log(props.msg) // => Hello World!
  emits('ok', '2')
  console.log(props.msg) // => Hello World!
}, 1000)
</script>

<template>
  <div>
    {{ msg }}
  </div>
</template>

Meanwhile, in vueuse.toRefs#objectRef.value, the data is also read in this way, which led to the occurrence of the problem.

const newObject = { ...objectRef.value, [key]: v }
Object.setPrototypeOf(newObject, Object.getPrototypeOf(objectRef.value))
objectRef.value = newObject

@ilyaliao
Copy link
Member

You might find some help here: https://github.com/orgs/vuejs/discussions/10538

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants