2

With this parent...

<template>
    <h2>Parent</h2>
    {{ parent.a }}
    {{ parent.b }}
    <ChildComponent :data="parent" />
</template>

<script setup>
import { reactive } from 'vue'
import ChildComponent from './components/ChildComponent.vue'
const parent = reactive({ a: 1, b: 2 })
</script>

And this child...

<template>
    <h2>Child component</h2>
    <p>{{ child.a }}</p>
    <p>{{ child.b }}</p>
    <input type="text" v-model="child.b" />
</template>

<script setup>
import { reactive } from 'vue'
const props = defineProps(['data'])
const child = reactive(props.data)
child.a = 'why do i update the parent?'
</script>

Why is the data on the parent being updated here? I thought that with binding of the 'data' prop being one-way, I would need an emit to send the data back to the parent? Instead any changes to the child object in the child component is updating the parent object in the parent.

In the documentation it says

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 in JavaScript objects and arrays are passed by reference, and it is unreasonably expensive for Vue to prevent such mutations.

But from my example, a and b aren't nested?

paddyfields
  • 1,466
  • 2
  • 15
  • 25
  • I can think of this statement- reactive returns a reactive copy of the object. The reactive conversion is "deep".It is recommended to work exclusively with the reactive proxy and avoid relying on the original object. Second, while the object returned from reactive is deeply reactive (setting any value to the object will trigger a reaction in Vue) – Neha Soni Feb 02 '23 at 10:12
  • So, my assumption is, using reactive api could be the reason. You can try de-structuring the reactive object, like, `const parent = {...reactive({ a: 1, b: 2 })}` and check if this is the culprit. – Neha Soni Feb 02 '23 at 10:14
  • Thank you for your comment although destructuring `reactive` will lose reactivity altogether. You are right though that the use of `reactive` was the issue - as it is creating a reactive copy of the original.. i'll add an answer. – paddyfields Feb 02 '23 at 10:29

2 Answers2

1

Further reading and I've found that it's the use of reactive on the child that is the issue. It is creating a reactive copy of the original (a reference), so any updates to the copy were affecting both. I needed to use ref instead:

<template>
    <div>
        <h2>Child component</h2>
        <p>{{ a }}</p>
        <p>{{ b }}</p>
        <input type="text" v-model="b" />
    </div>
</template>

<script setup>
import { ref } from 'vue'
const props = defineProps(['data'])

const a = ref(props.data.a)
const b = ref(props.data.b)
</script>
paddyfields
  • 1,466
  • 2
  • 15
  • 25
1

The reason is that JS treats an object like a reference. A more accurate term is call-by-sharing.

So when you modify a props object inside a child component, you actually modify the same object of the parent component regardless you are using reactive or not

Let's consider this code snippet:

<template>
    <h2>Child component</h2>
    <p>{{ child.a }}</p>
    <p>{{ child.b }}</p>
    <input type="text" v-model="child.b" />
</template>

<script setup>
import { reactive } from 'vue'
const props = defineProps(['data'])
const child = props.data // <--- No need to use reactive here
child.a = 'why do i update the parent?'
</script>

The data of the parent component will be still updated along with the child component regardless of the use of reactive. So, to avoid side effects, Vue recommended never mutating props directly. You should use event emitter instead

Duannx
  • 7,501
  • 1
  • 26
  • 59
  • Thank you, the 'object like a reference' explanation makes sense. To be honest the way you have described was the first attempt I made, but eslint throws an error that: `Getting a value from the props in root scope of – paddyfields Feb 03 '23 at 10:35
  • 1
    @paddyfields The term `reactivity` in that warning is in the scope of one-way-down binding. It means your variable will not be updated if the parent updates the child's props value. Even using `ref(props.data.b)` like your answer, it still will not be updated when the props are updated. – Duannx Feb 03 '23 at 10:58
  • 1
    So keep in mind that, if you want to use the value of a prop with reactivity, use it directly or through a computed. If you want to update a prop value, use event emitter – Duannx Feb 03 '23 at 11:00
  • 1
    This has really helped me get my head around it - thanks. – paddyfields Feb 03 '23 at 11:17