Search code examples
vue.jsvue-reactivitycomposable

vue.js 3 composable and reactivity


Given the following composable 'useNumbers.js' in vue.js 3, I'd like to have 'numbers' in the components reactive. But adding new values to numbers in 1 component is not reflected in the 2nd component.

useNumbers.js:

import { ref } from 'vue';

export default function() {
    const numbers = ref([1, 2, 3, 4, 5]);

    const addNumber = num => {
        numbers.value.push(num);
    }

    const filterNumber = minNum => {
        return numbers.value.filter(curNum => curNum >= minNum);
    }

    return { numbers, addNumber, filterNumber }
}

Component A:

<template>
    <div>
        <h1>Child component</h1>
        <p>{{ numbers  }}</p>
        <button @click="addNumber(45)">Add 45</button>
        <div class="line"></div>
        <GrandChild />
    </div>
</template>


<script setup>

import useNumbers from '../composables/useNumbers';
import GrandChild from './GrandChild.vue';

const { numbers, addNumber } = useNumbers()

</script>

Component B:

<template>
    <div>
        <h1>GreatGrandChild component</h1>
        <p>{{  numbers }}</p>
        <div class="line"></div>
    </div>
</template>

<script setup>

import useNumbers from '../composables/useNumbers';

const { numbers } = useNumbers();

</script>

Whenever I click the button in Component A (which pushes 45 to the array, the change is not reflected in component B. I know, that deconstruction like

const { numbers, addNumber } = useNumbers()

destroys reactivity. I've played around using toRef and toRefs, but got no solution so far.


Solution

  • To keep the same state across multiple useNumbers instances, you can lift the state outside of the function and then it will only be created once.

    import { ref } from 'vue';
    
    const numbers = ref([1, 2, 3, 4, 5]);
    
    export default function() { 
      ...
      return { numbers }
    }
    

    Vue Playground example