Skip to content
Advertisement

How to mutate props in Vue3 and have the parent computed setter called

I know this is an anti pattern, but it would simplify things a lot. And furthermore I’m just curious if it’s possible. When I call test1() I get the following warning: Set operation on key "items" failed: target is readonly. and nothing else happens. When I call test2() items change within the child AND the parent, BUT the computed setter ist not called. Why?

Why is the computed setter not called? Is it possible to mutate the props within the child and still have the computed setter called?

I’ve got the following parent component:

<template>
  <div class="m-10 bg-white">
    parent: {{ items }}
    <br />
    <br />
    <TestComponent2 :items="items" />
  </div>
</template>

<script lang="ts" setup>
import { computed, ref } from "vue";
import TestComponent2 from "./TestComponent2.vue";

const container = ref({
  items: ["a", "b", "c"],
});

const items = computed({
  get: () => {
    return container.value.items;
  },
  set: (v) => {
    console.log("set", v);
    container.value.items = v;
  },
});
</script>

And this is the child component:

<template>
  <div>
    child: {{ items }}
    <br />
    <br />
    <button @click="test1()">test1</button>
    <br />
    <button @click="test2()">test2</button>
  </div>
</template>

<script lang="ts" setup>
import { defineProps, toRefs } from "vue";

const props = defineProps({
  items: {
    type: Array,
    required: true,
  },
});

const { items } = toRefs(props);

const test1 = () => {
  items.value = [];
};

const test2 = () => {
  items.value.pop();
};
</script>

Advertisement

Answer

You’re likely looking for v-model:items="items".

The gist:

parent.vue:

<child v-model:items="items" />

child.vue:

emit('update:items', value);

Note: both items.pop() and items.push() will mutate the value in both parent and child, but they won’t trigger an assignment.

For that, you have to:

const copy = [...items];
copy.push('new'); // or pop(), splice()...
// if inside child:
emit('update:items', copy);

Docs here

Working example here


Technically,

<child v-model:items="items" />

is shorthand for:

<child
  :items="items"
  @update:items="value => items = value"
/>

In you don’t specify the child variable name (in our case items), it defaults to modelValue.

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement