Vue: mutating a prop warning and one-way data flow
Fix the Vue avoid-mutating-prop warning by emitting events, using local state, and modelling one-way data flow correctly.
Symptoms
- Avoid mutating a prop directly since the value will be overwritten
- A child component change is reverted on the next parent render
- Vue 3 throws reading a read-only computed for a mutated prop
Possible causes
- The child assigns to a prop instead of emitting an event
- A parent object is mutated through a shared reference
- The prop is used as v-model without an update handler
- A watch writes back into the prop instead of emitting
Diagnosis steps
Locate the assignment to a prop inside the child
Interpretation
Direct assignment props.x = ... is the trigger; the parent owns the value.
Replace with an emit or local copy
Interpretation
Emitting an event or copying into local ref restores one-way flow.
For v-model, check the update event name
Interpretation
v-model requires the child to emit update:modelValue for the value to flow back.
Root cause
Vue enforces one-way data flow: a parent passes data down and the child communicates changes up through events. Mutating a prop breaks that contract because the parent will overwrite the value on its next render, and a child cannot own state it does not control. Deep mutation through a shared object reference is the same violation, one level removed.
Fix
Emit an event for changes the parent should apply, or copy the prop into local state when the child needs an editable buffer. Use v-model with the correct update event for two-way bindings, and avoid mutating objects received as props by cloning before change. Treat props as read-only inputs and lift shared state to the nearest common owner.
Verify the fix
Change the value in the child; no mutation warning appears, the parent applies the update through the emitted event, and the value persists across re-renders.
Symptoms
Vue logs a warning that a prop is being mutated, or a change made in a child silently reverts. In Vue 3 the prop object is read-only, so writes may throw.
Diagnosis
Find the assignment in the child:
// child.vue — do not do this
props.count++;
The parent owns count, so any write here is overwritten on the next render.
Fix
Emit the change, or keep a local copy:
// child.vue
const emit = defineEmits(['update:count']);
function increment() {
emit('update:count', props.count + 1);
}
// or an editable local buffer
const local = ref(props.count);
<Child v-model:count="count" />
Verification
Increment in the child; no mutation warning appears and the parent value updates through the event. The value survives a re-render, confirming the flow is now parent-owned.
Additional Notes
Vue enforces one-way data flow so that ownership of state stays with the component that renders it, which is why a prop write is overwritten on the next parent render. Emitting an event keeps the parent authoritative and makes the change traceable, while a local copy suits a child that needs an editable buffer synchronised back only when appropriate. The same rule forbids mutating a shared object received as a prop, so cloning before change avoids surprising the parent, and using the correct model update event keeps two-way bindings working without violating the contract.