You install Vue3 and you decide to use the composable and the composition api to be faster and follow good practice, but something seems wrong and the destructured constant from the composable doesn’t update when your prop update? I got you!

Here is a simple example of the problem:

Composable

import { computed } from 'vue';

export const useDropDown = (props) => {
  const isSelectedOption = computed(() =>
    props.options.map(option => option === props.selectedOption));

  return {
    isSelectedOption,
  };
};

View

<template>
  <button
    v-for="(option, idx) in options"
    :key="idx"
    @click="$emit('on-select', option)"
  >
    <div
      :class="{ 'selected-option': isSelectedOption[idx] }"
    >
      <div v-if="!!option.name" v-html="option.name"/>

      <div v-html="option.address" />
    </div>

    <AppIcon
      v-if="isSelectedOption[idx]"
      dropdown
      icon-name="check"
    />
  </button>
</template>

<script setup>
import { useDropDown } from '@/composables/dropdown-modal';
import AppIcon from '@/components/app/AppIcon';

const emit = defineEmits(['on-select']);
const props = defineProps({
  options: {
    type: Array,
    required: true,
  },
  selectedOption: {
    type: Object,
    required: true,
  },
});

const { isSelectedOption } = useDropDown(props);
</script>

Everything seems good and it should work, right? We use reactive props and a computed for a composable, so why does the value of isSelectedOption never change?

Here’s the answer: The lifecycle hook

The setup (composition api) is run before everything. It takes the place of the beforeCreated/Created since they don’t exist anymore. So, what happens here is that the code is run on the created and never passes in the composable anymore because the prop changes don’t re-render the composable. Hence isSelectedOption keeping its initial value.

To keep this reactivity going, we will need to add something to watch the prop and update isSelectedOption.

You should NOT use onUpdated in this case because if you do, you update isSelectedOption in it and it calls an update so it re-renders… you see where I’m going: Infinite loop.

The solution is to add a watch. There is also another problem. The destructured element doesn’t go out of the watch. We need a new variable to manage the value with the reactivity we want. I also add a ref, just to be sure the reactivity is at its maximum!

<script setup>
import { computed, ref, watch } from 'vue';
import { useDropDown } from '@/composables/dropdown-modal';

import AppIcon from '@/components/app/AppIcon';

const emit = defineEmits(['on-select']);
const props = defineProps({
  options: {
    type: Array,
    required: true,
  },
  selectedOption: {
    type: Object,
    required: true,
  },
});

let { isSelectedOption } = useDropDown(props);
let selectedOptionArray = ref(isSelectedOption.value);

watch(() => props.selectedOption, (value) => {
  ({ isSelectedOption } = useDropDown(props));
    selectedOptionArray.value = isSelectedOption.value;
});
</script>

Now, we can use selectedOptionArray instead of isSelectedOption and have reactivity to the infinite and beyond! 🚀