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

4.2k Views Asked by At

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>
4

There are 4 best solutions below

1
On

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

2
On

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: []
  }
})
0
On

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

0
On

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);
        }