Javascript is required
ยท
4 min read

Vue Tip: Avoid Mutating a Prop Directly

Vue Tip: Avoid Mutating a Prop Directly Image

If you are using Vue 3 + ESLint and trying to mutate a prop in your Vue component, you should see the following error:

ESLint error

Unexpected mutation of "todo" prop. eslintvue/no-mutating-props

If you are using Vue 2, Vue will throw the following error/warning in your browser's console:

Message in browser console

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.

Explanation

First, we look at the official documentation to understand why Vue throws that error.

Why do we see the error

When objects and arrays are passed as props, while the child component cannot mutate the prop binding, it will be able to mutate the object or array's nested properties. This is because JavaScript objects and arrays are passed by reference, and it is unreasonably expensive for Vue to prevent such mutations.

Why is it a problem

The main drawback of such mutations is that it allows the child component to affect the parent state in a way that isn't obvious to the parent component, potentially making it more difficult to reason about the data flow in the future. As a best practice, you should avoid such mutations unless the parent and child are tightly coupled by design.

In most cases, the child should emit an event to let the parent perform the mutation.

Demo

Let's look at a code example. I'm using a Todo app to demonstrate the error. The source code is interactively available on StackBlitz:

Let's start by taking a look at the TodoList.vue component:

TodoList.vue
1<script setup lang="ts">
2import { ref, Ref } from 'vue'
3import TodoItem from './TodoItem.vue'
4
5const todos: Ref<Array<{ id: number; name: string; completed: boolean }>> = ref([
6  { id: 1, name: 'Buy milk', completed: false },
7  { id: 2, name: 'Clean house', completed: false },
8])
9</script>
10
11<template>
12  <h2>Todo</h2>
13  {{ todos }}
14  <div v-if="todos.length > 0" class="todo-list">
15    <TodoItem v-for="todoItem of todos" :key="todoItem.id" :todo="todoItem" />
16  </div>
17  <span v-else>Nothing todo</span>
18</template>
19
20<style scoped>
21.todo-list {
22  display: flex;
23  flex-direction: column;
24}
25</style>

TodoList.vue contains a reactive variable todos that include an array of Todo items. In the template, each item is rendered via TodoItem.vue component:

TodoItem.vue
1<script setup lang="ts">
2defineProps<{
3  todo: { id: number; name: string; completed: boolean }
4}>()
5</script>
6
7<template>
8  <div class="container">
9    <p>{{ todo.name }}</p>
10    Completed?
11    <input v-model="todo.completed" type="checkbox" />
12  </div>
13</template>
14
15<style scoped