vue.jsvue-reactivity

vue nested ref modification / why[not] loosing reactivity


I am quite a newbee to vue and would like to ask for an explanation how to cleanly accomplish this szenario:

I have an ref object which is a nested object with arrays and other objects in it. there is for example an array of positions which will be rendered as list of childcomponents. these childcomponents only get the id/index of the array and have to pull the data from the store because i want to avoid prop drilling (there are in reality multiple child layers)

When i update the object in specific ways i loose the reactivity in these components. if i update the object recursive and update the scalars one by one the reactivity is preserved. with loosing reactivity i mean, the object is updated and the parent component shows the new values, but the child component shows the initial values.

I put together an simplified example where instead of an pinia store i use the window object and reduced the object to one array-property and show the update with example functions.


Example data:

export const entityA = {
    propAr: [
        { sc1: 'foo' },
    ],
};

export const entityB = {
    propAr: [
        { sc1: 'bar' },
    ],
};

parent component:

<script setup lang="ts">

window.entity = ref(cloneDeep(entityA));
</script>

<template>
    <div>
        <child :id="0" />
        <pre class="card">{{ window.entity }}</pre>
    </div>
</template>

child component:

<script setup lang="ts">

const props = defineProps({
    id: ...,
});
const element = window.entity.value.propAr[props.id];
</script>

<template>
    <pre class="card">{{ element }}</pre>
</template>

Here are the update methods i tried:

function working() {
    const destObj = window.entity.value;
    const srcObj = cloneDeep(entityB);

    destObj.propAr[0].sc1 = srcObj.propAr[0].sc1;
    destObj.propAr[1].sc1 = srcObj.propAr[1].sc1;
}

function notWorking1() {
    window.entity.value = cloneDeep(entityB);
}

function notWorking2() {
    Object.assign(window.entity.value, cloneDeep(entityB));
}

My question is, if you could explain why one is working and the other not. and what would be the correct way to handle this.


Solution

  • This breaks the reactivity:

    const element = window.entity.value.propAr[props.id];
    

    element holds a reference to original { sc1: 'foo' } object through the lifespan of child component.

    For a read-only value, it should be a computed:

    const element = computed(() => entity.value.propAr[props.id]);
    

    Also using globals for local values in modular environment is a bad practice. If entity is supposed to be available to a specific hierarchy of components, the store can be provided through provide/inject.

    In a parent:

    const entity = ref(...);
    provide('entity', entity);
    

    In a child:

    const entity = inject('entity');
    

    If the scope is not important, it can be global store that is defined in a separate module, that a module is executed once provides only one instance per application.