robinvrd
robinvrd

Reputation: 1848

Helper function in Vuex

I am new in Vuejs, but I'm coming from React, just trying to use Vuex here.

I built my store with my state containing an array cart.

const state = () => ({
    cart: []
})

And I want a function isInCart(product) that returns either the product is in the current cart or not.

<span v-if="isInCart(product)">hello</span>

Am I supposed to use the getters of my store ?

const getters = {
    isInCart: (state) => (product) => state.cart.find(_product => _product.id === product.id) !== undefined,
}

And then use it like this in my component ?

computed: {
    ...mapGetters(['isInCart'])
},

But I have : Computed property "isInCart" was assigned to but it has no setter..

EDIT

I forgot to prefix the getters with its module name.

...mapGetters({ isInCart: 'shop/isInCart' }) // module name = shop

Upvotes: 0

Views: 1008

Answers (2)

tao
tao

Reputation: 90013

Ref: Am I supposed to use the getters of my store ?

Yes. ...mapGetters({}) only reads getters from your store.

If you want to read state properties, (i.e: cart), use

computed: {
  ...mapState({ cart })
}

...or

computed: {
  cart() {
    return this.$store.state.cart
  }
}

Ref: Computed property "isInCart" was assigned to but it has no setter.

It's because you are trying to assign a value to isInCart inside the component where you're importing it.

You can't do that. If you ever want to actually set a store getter to a particular value, here's the pattern to use:

computed: {
  yourGetter: {
    get: function() {
      return this.$store.getters['yourGetter']
    },
    set: function(value) {
      this.$store.dispatch('actionCommittingMutationChangingYourGetter', value)
    }
  }
}

Note: if you only need that getter in this component, it doesn't have to be a store getter. You can get it directly from state:

computed: {
  whatever: {
    get: function() {
      return this.$store.state.whatever;
    },
    set: function(value) {
      this.$store.dispatch('setWhatever', value);
    }
  }
}

Note: If your getter returns a function (as in your case) you can safely call it after mapping it in the component as computed: (i.e: this.isInCart(product)). It's unusual to use a computed with a param, but if it returns a fn that fn can be called.

On another note, in many cases, you can skip the dispatch and commit directly. But committing directly from outside of a store is not considered "safe" and in certain cases you'll get errors for doing it.
Out of principle, I would advise against it, although it does work in some cases.


Ref how to write your fn cleaner. This discussion involves a lot of personal preference as well as coding standards you or your team might have in place. Which makes the discussion per-se off topic on Stack Overflow.

Just for kicks, here's how I'd write it. But it's not because it's better or cleaner. It's based on purely personal preference and it's likely faster than what you have by an insignificant amount:

const getters = {
  isInCart: state => product => state.cart.map(p => p.id) 
    .findIndex(id => id === product.id) > -1
}

Upvotes: 1

J&#225;nos Veres
J&#225;nos Veres

Reputation: 199

Take a look at this answer for a Vuex getter with parameters: Pass params to mapGetters

Edit

I agree, it is not clean and not simple. I would much rather redesign this:

Option 1

You can use your cart from Vuex and calculate directly, keeping reactivity:

<template>
  <span v-if="$store.state.cart.some(p => p.sku === productSku)">hello</span>
  <!—- both work efficiently —>
  <span v-if="hasProduct(productSku)">hello</span>

</template>
<script>
export default {
  methods: {
    hasProduct(sku) {
      return this.$store.state.cart.some(p => p.sku === sku)
    }
  }
}
</script>

That being said, this is not a computed property. You want to have multiple return values of a computed property, but it is not designed for this purpose.

Option 2

Your best option would be to introduce a product relevant version of this component:

<template>
  <span v-if="hasProduct">hello</span>
<template>

<script>
export default {
  name: „ProductRelevantComponent“
  props: {
    productSku: {
      type: String,
      required: true
    }
  },
  computed: {
    hasProduct() {
      return this.$store.state.cart.some(p => p.sku === this.productSku)
    }
  }
}
</script>

Upvotes: 0

Related Questions