Vue3 | Pinia - Watching storeToRefs in composable function does not work
Asked Answered
E

4

19

I'm trying to understand the purpose of composables. I have a simple composable like this and was trying to watch state from a Pinia store where the watch does not trigger:

import { ref, watch, computed } from "vue";
import { storeToRefs } from "pinia";
import useFlightsStore from "src/pinia/flights.js";
import usePassengersStore from "src/pinia/passengers.js";

export function useFlight() {
    const route = useRoute();

    const modalStore = useModalStore();
    const flightsStore = useFlightsStore();
    const { selection } = storeToRefs(flightsStore);

    const passengersStore = usePassengersStore();
    const { passengers, adults, children, infants } =
        storeToRefs(passengersStore);

    watch([adults, children, infants], (val) => console.log('value changes', val))


Where as the same thing in a Vue component works as expected.

So we cannot watch values inside composables?

Escorial answered 9/4, 2022 at 17:50 Comment(0)
I
28

I think you can watch values inside composables.

But, to watch a pinia state it has to be inside an arrow function:

watch(() => somePiniaState, (n) => console.log(n, " value changed"));

It's like watching a reactive object.


I believe this should be documented better. In Pinia documentation we can read how to watch the whole store or how to subscribe to a store but not how to watch a single state property inside a component or composable.

Also, the docs are somewhat shy in explaining that you can watch a property inside a store using setup() way of describing a store.

More on this here: https://github.com/vuejs/pinia/discussions/794#discussioncomment-1643242


This error also silently fails (or does not execute), which is not helpful...

Inquest answered 20/4, 2022 at 9:45 Comment(6)
Tried this and still no resultsEscorial
Can confirm, so far state variables don't have any reactivity outside the store for me, but getters do though.Saddlebacked
Is it using the option api? I changed my code to use the script setup and the reactivity works out of the box. It's really easy to access states as well. It simplified my code a lot, even though I love the option api.Zarate
Where and how is the line with watch(()=>somePiniaState...) to be integrated in a component? A more detailed example would be nice...Villainous
In Vue 3 the syntax seem to have changed to watch(somePiniaState, (n) => console.log(n, " value changed"));. It wouldn't work for me using () => somePiniaState.Outwardbound
Anyone wrestling with this, another thing to try is to wrap the Pinia state in a computed, then watch the computed.Bolometer
B
15

I needed to watch a specific state attribute in one of my components but I didn't find my use case on the official documentation. I used a mix between a watch and storeToRefs to do it.

<!-- Option API -->
<script>
import { usePlaylistsStore } from '@/stores/playlists'
import { storeToRefs } from 'pinia'
export default {
    name: 'PlaylistDetail',

    setup() {
        const playlistsStore = usePlaylistsStore()
        const { selectedGenres } = storeToRefs(playlistsStore)
        return { selectedGenres }
    },
    watch: {
        selectedGenres(newValue, oldValue) {
            // do something
        }
    }
}
</script>

<!-- Composition API -->
<script setup>
import { usePlaylistsStore } from '@/stores/playlists'
import { storeToRefs } from 'pinia'
import { computed, watch } from 'vue'

const playlistsStore = usePlaylistsStore()
const { selectedGenres } = storeToRefs(playlistsStore)

watch(() => selectedGenres, (newValue, oldValue) => {
    // do something
})
</script>
Bacterium answered 7/6, 2022 at 12:7 Comment(1)
This also works with composition API.Mousy
F
5

There are 3 options for you to be able to use the watch in this context:


Option 1: Return in setup

    setup() {
        const store = useStore()
        const { piniaVariable } = storeToRefs(store)
        return { piniaVariable }
    },
    watch: {
        piniaVariable (newValue, oldValue) {
            // do something
        }
    }


Option 2: Direct Access

watch: {
        'yourStore.piniaVariable' (newValue, oldValue) {
            // do something
        }
    }

Option 3: Computed Property

computed: {
    piniaVariable() {
        return useYourStore.piniaVariable
    }

    watch: {
        piniaVariable (newValue, oldValue) {
            // do something
        }
    }

}

Feeze answered 28/4, 2023 at 16:56 Comment(0)
A
1

By default watches in Vue are shallow watches. In order to watch the nested property change you have to set deep option of the watch to true.

watch(state, (newState, oldState) => {
// do stuff
}, { deep: true})

more on this

Atlee answered 26/3, 2023 at 5:1 Comment(1)
This isn't true. To quote your link "When you call watch() directly on a reactive object, it will implicitly create a deep watcher - the callback will be triggered on all nested mutations". There are cases where the watcher isn't deep, but it isn't OP's case.Oligosaccharide

© 2022 - 2024 — McMap. All rights reserved.