Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3 debugging reference for reactivity issues, computed errors, watcher bugs, async failures, and SSR hydration problems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/definemodel-object-mutation-no-emit.md
1---2title: defineModel Object Properties Must Be Replaced, Not Mutated3impact: HIGH4impactDescription: Mutating object properties via defineModel doesn't emit update events, breaking parent sync5type: gotcha6tags: [vue3, v-model, defineModel, objects, reactivity, two-way-binding]7---89# defineModel Object Properties Must Be Replaced, Not Mutated1011**Impact: HIGH** - When using `defineModel()` with objects or arrays, directly mutating nested properties like `model.value.prop = x` does NOT emit the `update:modelValue` event. The parent component never receives the change notification, causing silent sync failures.1213This happens because Vue only detects when the `model.value` reference itself changes, not when properties of the object are mutated in place.1415## Task Checklist1617- [ ] Never mutate object properties directly: `model.value.prop = x`18- [ ] Always create a new object reference when updating: `model.value = {...model.value, prop: x}`19- [ ] For arrays, use spread or slice: `model.value = [...model.value, newItem]`20- [ ] Consider using structuredClone for deeply nested objects2122**Incorrect - Mutation without event emission:**23```vue24<script setup>25// Child component with object v-model26const model = defineModel<{ name: string; age: number }>()2728function updateName(newName: string) {29// WRONG: This mutates the object in place30// Parent receives NO update:modelValue event!31model.value.name = newName32}3334function addToList() {35// WRONG: Push mutates the array36model.value.items.push('new item') // Parent not notified37}38</script>39```4041**Correct - Replace object reference to trigger event:**42```vue43<script setup>44const model = defineModel<{ name: string; age: number }>()4546function updateName(newName: string) {47// CORRECT: Create new object reference48// This triggers update:modelValue event to parent49model.value = {50...model.value,51name: newName52}53}5455function addToList() {56// CORRECT: Create new array reference57model.value = {58...model.value,59items: [...model.value.items, 'new item']60}61}62</script>63```6465## Deep Nesting Requires Full Path Replacement6667```vue68<script setup>69const model = defineModel<{70user: {71address: {72city: string73}74}75}>()7677// WRONG: Deep mutation78model.value.user.address.city = 'New York'7980// CORRECT: Replace entire chain81model.value = {82...model.value,83user: {84...model.value.user,85address: {86...model.value.user.address,87city: 'New York'88}89}90}9192// ALTERNATIVE: Use structuredClone for complex updates93function updateCity(city: string) {94const updated = structuredClone(model.value)95updated.user.address.city = city96model.value = updated // New reference triggers event97}98</script>99```100101## Race Condition Warning with Spread Operator102103When multiple updates occur rapidly, earlier changes can be lost:104105```vue106<script setup>107const model = defineModel<{ a: string; b: string }>()108109// CAUTION: Race condition if called in same tick110function updateBothWrong() {111model.value = { ...model.value, a: 'new-a' } // First update112model.value = { ...model.value, b: 'new-b' } // May use stale model.value!113}114115// CORRECT: Batch updates into single assignment116function updateBothCorrect() {117model.value = {118...model.value,119a: 'new-a',120b: 'new-b'121}122}123</script>124```125126## Alternative: VueUse's useVModel with Deep Option127128For complex objects, consider VueUse:129130```vue131<script setup>132import { useVModel } from '@vueuse/core'133134const props = defineProps<{ modelValue: { name: string } }>()135const emit = defineEmits(['update:modelValue'])136137// Deep tracking with passive updates138const model = useVModel(props, 'modelValue', emit, { deep: true, passive: true })139140// Now direct mutations work141model.value.name = 'New Name' // Properly syncs with parent142</script>143```144145## Reference146- [Vue.js Component v-model](https://vuejs.org/guide/components/v-model.html)147- [GitHub Discussion: defineModel with objects](https://github.com/orgs/vuejs/discussions/10538)148- [SIMPL Engineering: Vue defineModel Pitfalls](https://engineering.simpl.de/post/vue_definemodel/)149