Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

jdw5's avatar
Level 4

Ref not persisting value from composable

Hi,

I have a composable which is used to manage the state of a mode across many components. It toggles a flag to designate that a component can be clicked, and then updates the field it was called from. This all works, except the change to the given model does not persist.

Here is a shortened version of the implementation:

<template>
    <button @click="onClick">
        {{ model ?? 'Nothing selected' }}
	</button>
</template>

<script setup>
import { useSelect } from '@/Composables'

const model = defineModel()
const select = useSelect()

// ...

const onClick = () => {
    select.begin({
	    // Options
	})
    select.bus.on(listener)
}

const listener = () => {
    model.value = select.item.value
    console.log(model.value, select.item.value)
    select.bus.off(listener)
}
</script>

The details of the composable I don't believe are critical to this. It returns an destructurable object with functions, refs and the bus used.

When the select mode is toggled off, it uses the @vueuse/core useEventBus composable and emits an event. This is listened to, and the model value should be updated. When logging this interaction for the selected value, the values are always the same (EG: it'll log 'a a'). However, the mutation to the model value never persists. The template reference to it never updates, a watcher is never executed - it is only the same within the listener callback.

Has anyone encountered a similar issue, or have any possible solutions? The only thing that has worked in an earlier version is emitting events up from the component, and handling everything in a shared parent. This did work, but the needed complexity increased and now this is no longer viable. This issues applies to any form of reactivity in Vue, as well as passing callbacks directly to the composable instead of by listener.

0 likes
1 reply
LaryAI's avatar
Level 58

It sounds like you're encountering an issue where the reactivity system in Vue isn't updating your template when the model value changes. This can happen for a few reasons, but let's focus on ensuring that the model is properly reactive and that the event listener is correctly updating it.

Here is a revised version of your code with some adjustments to ensure reactivity:

  1. Ensure that model is a reactive reference.
  2. Make sure the event listener is properly set up and cleaned up.
<template>
    <button @click="onClick">
        {{ model.value ?? 'Nothing selected' }}
    </button>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { useSelect } from '@/Composables';

const model = ref(null); // Ensure model is a reactive reference
const select = useSelect();

const onClick = () => {
    select.begin({
        // Options
    });
    select.bus.on('itemSelected', listener); // Use a named event for clarity
};

const listener = () => {
    model.value = select.item.value;
    console.log(model.value, select.item.value);
    select.bus.off('itemSelected', listener); // Clean up the listener
};

// Clean up the event listener when the component is unmounted
onUnmounted(() => {
    select.bus.off('itemSelected', listener);
});
</script>

Explanation:

  1. Reactive Reference: Ensure that model is a reactive reference by using ref(null). This makes model reactive and ensures that changes to model.value will trigger updates in the template.

  2. Event Listener: Use a named event ('itemSelected') for clarity and consistency. This helps in managing the event listeners more effectively.

  3. Clean Up: Use onUnmounted to clean up the event listener when the component is unmounted. This prevents potential memory leaks and ensures that the listener is properly removed.

  4. Template Binding: Use model.value in the template to access the reactive value of model.

By making these adjustments, you should see the model value update in the template when the event is emitted and the listener updates the model.

Please or to participate in this conversation.