简体   繁体   中英

Vue component accessing data before it is available in Vuex store

I have a component named ProductArea which displays products loaded from the Prismic API . The products loaded are dependant on a category which is selected by the user in a sidebar.

I'm using Vuex and struggling to come up with a flow that avoids a situation where category is not yet available in my store ( category is also loaded from Prismic ).

Here is what the parent of ProductArea looks like:

<template>
  <div>
    <NavBar />
    <!-- <Header /> -->
    <main>
      <div v-if="!$fetchState.pending" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div class="flex-1 min-w-0 bg-white xl:flex">
          <Sidebar :navigation="navigation" />

          <ProductArea />
        </div>
      </div>
    </main>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
import NavBar from '@/components/NavBar.vue'
import Sidebar from '@/components/Sidebar.vue'
import Header from '@/components/Header.vue'
import CategoryHeader from '@/components/CategoryHeader.vue'
import ProductGrid from '@/components/ProductGrid.vue'
import { mapActions } from 'vuex'
import { mapGetters } from 'vuex'

export default {
  name: 'App',
  components: {
    Sidebar,
    NavBar,
    Header,
    CategoryHeader
  },
  data() {
    return {
      navigation: null
    }
  },
  async fetch() {
    const component = this
    await this.fetchCategories()
    .then(function(navigationResult) {
      const navigation = component.$store.getters.navigation
      component.navigation = navigation
    })
  },
  fetchOnServer: true,
  methods: {
      ...mapActions({ fetchCategories: 'fetchCategories', fetchProducts: 'fetchProducts' })
  }
}
</script>

I assumed having v-if=".$fetchState.pending" would prevent ProductArea from being created until category has been loaded into the store, however this doesn't seem to be the case.

Here is ProductArea :

<template>
    <div class="bg-white lg:min-w-0 lg:flex-1">
        <CategoryHeader :category="this.category" :products="this.products" />

        <div class="sm:p-6">
            <ProductGrid :category="this.category.primary.category" :products="this.products" />
        </div>
    </div>
</template>

<script lang="ts">
import { mapActions } from 'vuex'
import { mapGetters } from 'vuex'
import Locale from '@/types/locale'

export default {
    name: 'ProductArea',
    data() {
        return {
            category: this.$store.getters.category,
            products: Array
        }
    },
    async fetch() {
        const component = this

        await this.fetchProducts(this.category)
        .then(function(productsResult) {
            const products = component.$store.getters.products
            component.products = products
            console.log(products)
        })
    },
    fetchOnServer: true,
    methods: {
        ...mapActions({ fetchProducts: 'fetchProducts' })
    }
}
</script>

Here's the error I'm receiving:

Error in fetch(): TypeError: Cannot read property 'products' of undefined

This error is referring to the undefined category within the fetchProducts called via fetch on the ProductsArea component.

Can anyone point me in the right direction? What would be the optimal flow here to prevent category being accessed before it is available?

You could set a default category. If you don't want to do that, bring the Vuex category into the parent and only show <ProductArea> when it's defined:

Parent

<ProductArea v-if="category" />
computed: {
  ...mapGetters(['category'])
}

This is necessary because your v-if on $fetchState.pending only tests whether all the categories are loaded, but for the child component you also need to test that a category has been selected.

In fact, you can simplify all your code by mapping the getters instead of storing getters in variables, which is not a good practice. Those variables wouldn't be updated reactively when the getter changes. Instead, completely remove the data options from both components:

Parent

async fetch() {
  await this.fetchCategories();
}
computed: {
  ...mapGetters(['category', 'navigation'])
}

Child

async fetch() {
  await this.fetchProducts();
}
computed: {
  ...mapGetters(['category', 'products'])
}

Other improvements :

  • You can shorten the mapActions calls a bit:

    Parent: ...mapActions(['fetchCategories'])

    Child: ...mapActions(['fetchProducts'])

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM