Why are computed properties not reactive in my Vue 3 app?
Asked Answered
C

4

14

Issue

Vue 2: computed properties are updated after vuex store mutation.

Vue 3: computed properties are not updated after vuex store mutation.

Vuex 4

Purpose: Get posts from Firestore. Add to "postsArray". Commit mutation.

Note: The function "getNextPosts" is called from a (working) intersection observer which allows infinite-scrolling.

const postsArray: Array<string> = [];
const vuexStore = createStore({
  state: {
    posts: []
  },
  actions: {
    // See https://firebase.google.com/docs/firestore/query-data/query-cursors#paginate_a_query
    getFirstPosts({ commit }) {
      const getFirstPosts = async () => {
        const firstPostsQuery = firestore.collection("posts").limit(3);

        // Get FIRST posts.
        const firstPosts = await firstPostsQuery.get();

        // Add to postsArray.
        for (const doc of firstPosts.docs) {
          postsArray.push(doc.id);
        }

        // Add to vuex store.
        commit("addFirstPostsToVuex", postsArray);

        // Set reference.
        lastPostDownloaded = firstPosts.docs[2]; // 3rd post.
      };
      getFirstPosts();
    },
    getNextPosts({ commit }) {
      const getNextPosts = async () => {
        const nextPostsQuery = firestore
          .collection("posts")
          .startAfter(lastPostDownloaded)
          .limit(2);

        // Get NEXT posts.
        const nextPosts = await nextPostsQuery.get();

        // Add to postsArray.
        for (const doc of nextPosts.docs) {
          postsArray.push(doc.id);
        }

        // Add to vuex store.
        commit("addNextPostsToVuex", postsArray);

        // Update reference.
        lastPostDownloaded = nextPosts.docs[1]; // 2nd post.
      };
      getNextPosts();
    }
  },
  mutations: {
    addFirstPostsToVuex(state, value) {
      state.posts = value;
    },
    addNextPostsToVuex(state, value) {
      state.posts = value;
    }
  }
});

Computed properties

export default ({
  computed: {
    posts() {
      // List rendering.
      return vuexStore.state.posts;
    }
  }
});

v-for

<template>
  <div id="feed">
    <article class="post" v-for="post in posts" v-bind:key="post.id">
      <header class="info">
        {{ post }}
      </header>
    </article>
  </div>
</template>
Carry answered 7/12, 2020 at 5:14 Comment(7)
can you share the full code of the component where you have used the computed propertyOxfordshire
Also instead of importing the entire store you can use ...mapState in your computed propertyOxfordshire
What if you use vuex getters: vuex.vuejs.org/guide/getters.htmlLiz
please share the main.ts codeInositol
I'm interested to see a proper solution/explenation for this issue as well so I started a bounty.Commonly
What do you see in your devtools? Is the state updated there?Inez
I can not reproduce this.Moazami
C
1

There is a slight difference in defining the state inside Vuex between the old and new version.

**In Vuex3 state was just a prop with an Object while in Vuex4 it has return an Object or a function which returns an Object.**

When migrating from V3 to V4 you might at first not notice the difference because the V3 style kind of works in V4 as well. The difference shows when you've got modules and have multiple instances of them. Then the state has to be a function that returns an object to avoid state pollution (as commented by tony19).

Modules:

// Vuex 3.x Module
const moduleVUEX3 = {
    export const state = { ... }
}

// Vuex 4.x Module
const moduleVUEX4 = {
    export const state = () => ({ ... })
}

Single Store:

// Vuex 3.x
import Vuex from 'vuex'

const store = new Vuex.Store({
  state: { ... }
})


// Vuex 4.x
import { createStore } from 'vuex'

const store = createStore({
  state() {
    return { ... }
  }
})

Solution for this question would be:

const vuexStore = createStore({
  state: return {
    posts: []
  }
})
Commonly answered 21/10, 2021 at 10:26 Comment(2)
Vuex 4 state can be either Object or Function. And state being an Object instead of a Function isn't absolutely necessary, and it doesn't affect reactivity (demo). That said, if your app uses multiple instances of a module, using a Function is needed so that each instance has their own state.Melendez
@Melendez Thanks for pointing it out - I indeed have multiple module instances inside my app.Commonly
A
0

I usually use it like this

<script setup lang="ts">
import { computed, reactive,ComputedRef } from "vue";
import { useStore } from "vuex";
interface State {
  shadow: ComputedRef<boolean>
}
const store = useStore();

const state = reactive<State>({
  shadow: computed(() => store.state.shadow),
)}
</script>

When the status in vuex changes, the page will respond in time, hope it can help you

Arleenarlen answered 30/10, 2021 at 5:32 Comment(0)
S
-1

You could try to remove the postsArray, I assume it has something to do with reactivity.

try changing the mutation to

addFirstPostsToVuex(state, docId) {
      state.posts.push(docId);
    }

and then in the action you do

for (const doc of firstPosts.docs) {
          commit("addFirstPostsToVuex",doc.id);
        }
Scarfskin answered 27/10, 2021 at 19:35 Comment(0)
E
-2

This could be fixed if you use mapState function to map store's state to Vue component's state.

In the code above, that would be something like:

import { mapState } from "vuex";
import { defineComponent } from "vue";

export default defineComponent({
     computed: {
        ...mapState(["posts"])
     }
});

The v-for should work just fine now.

Reference: https://vuex.vuejs.org/guide/state.html and https://scrimba.com/scrim/c8Pz7BSK?pl=pnyzgAP

Electronic answered 7/12, 2020 at 7:6 Comment(1)
It doesen't work even with mapState.Commonly

© 2022 - 2024 — McMap. All rights reserved.