I have a component that gets data from the API and passes it to another component that displays it (passing down again to multiple child components). So I tried to split that information into multiple ref objects for passing down to each child component.
The problem is, doing that I loose reactivity... the new ref object does not update when parent component changes their data.
I created an example to show the problem.
ParentComponent
<template>
<p>All data: {{ data }}</p>
<child-component :data="data"></child-component>
</template>
<script setup>
import { ref } from "vue";
import ChildComponent from "./ChildComponent.vue";
let data = ref({
a: {},
b: {},
c: {}, // More properties...
});
setTimeout(() => { // FAKE API
data.value.a = {
name: "prop A",
description: "aaa"
};
data.value.b = {
name: "prop B",
description: "bbb"
};
data.value.c = {
name: "prop C",
description: "ccc"
};
// More properties updated...
}, 2000);
</script>
ChildComponent
<template>
<p>child component props: {{data}}</p>
<p>child component manipulated: {{manipulated}}</p>
</template>
<script setup>
import { ref } from "vue";
const props = defineProps(['data'])
let manipulated = ref({ // Only need a and b properties.
a: props.data.a,
b: props.data.b
})
</script>
Output when mount components
All data: { "a": {}, "b": {}, "c": {} }
child component props: { "a": {}, "b": {}, "c": {} }
child component manipulated: { "a": {}, "b": {} }
Output after updated parent component data:
All data: { "a": { "name": "prop A", "description": "aaa" }, "b": { "name": "prop B", "description": "bbb" }, "c": { "name": "prop C", "description": "ccc" } }
child component props: { "a": { "name": "prop A", "description": "aaa" }, "b": { "name": "prop B", "description": "bbb" }, "c": { "name": "prop C", "description": "ccc" } }
child component manipulated: { "a": {}, "b": {} }
Desired result after updated parent component data:
All data: { "a": { "name": "prop A", "description": "aaa" }, "b": { "name": "prop B", "description": "bbb" }, "c": { "name": "prop C", "description": "ccc" } }
child component props: { "a": { "name": "prop A", "description": "aaa" }, "b": { "name": "prop B", "description": "bbb" }, "c": { "name": "prop C", "description": "ccc" } }
child component manipulated: { "a": { "name": "prop A", "description": "aaa" }, "b": { "name": "prop B", "description": "bbb" } }
How can achieve that? With primitives works... but with objects there is missing something to maintain the reactivity...
CodePudding user response:
Option 1: Use deep watcher
You could use a deep watcher (i.e., watch
with the deep
flag) on props.data
that updates manipulated
with the new values of props.data
:
// ChildComponent.vue
import { watch } from 'vue'
⋮
watch(
() => props.data,
newValue => {
const { a, b } = newValue
manipulated.value = { a, b }
},
{ deep: true }
)
Option 2: Use computed
If manipulated
were just a read-only prop, you could switch from the ref
and watch
to a computed
instead:
import { computed } from 'vue'
const props = defineProps(['data'])
let manipulated = computed(() => ({
a: props.data.a,
b: props.data.b,
}))