Vuex: Vuex getters

Created on 14 Apr 2016  路  27Comments  路  Source: vuejs/vuex

Hi guys. There's been something on my mind lately about Vuex that I'll try my best to explain here.

I understand that Vuex getters are meant to return a sub-set of the state and that's fine. I also kinda understand why they can't take arguments... because reasons (SSR) and stuff.

On a component, if I want to return, say, a product from the state I'd access it directly, something like state.products[productId] and that it would be fine.

Now say I would like to return a product by productName instead and let's say I would do it by iterating on the product array state until I find it. This is still fine and I would create a method on the component to deal with it. If I would like to share getProductByName method, I would put it onto a mixin and everything would be alright.

But... Wouldn't it be nicer if Vuex could provide me with a way of creating a file of "special" getters so that everything state related (even just reading that state) would be managed on the "Vuex side of things" ?

I think that literally what I'm saying is that Vuex could have a way of letting me pass arguments to "getters". Just for example sake, current getters would become reducers (because they return a sub-set of the state) and then the "new" getters would actually be able to take arguments to compute off. (I'm totally aware that renaming is not an option, since it would break compatibility, but hopefully it was useful for an argument sake) (also, sorry about reducers naming, because I didn't want to start a discussion on what Flux is not and what Vuex is 馃槃 ).

Any thoughts ?

discussion

Most helpful comment

If we want to allow extra arguments to getters, we can define the getter returning the function that receives extra arguments.

retrievePrinterPing: state => printer => {
  return state.pings[printer.site][printer.hostname];
}

All 27 comments

I'm not sure if I understand you correctly, can you give some code to illustrate what kind of API you have in mind?

@ruiposse @yyx990803 gotcha! :smile:

I think he wants to be able to define getters with parameters, basically.

Woudn't it be awesome if, besides getters, we could have also computed properties and methods?

Basically, yes.

Anyway, thank you @yyx990803 for your answer on this topic yesterday at the London Vue.js Meetup 馃槃

I totally got your point that productName could actually be part of the state and updated accordingly which would in turn update the sub-set of the state via the getter. That works and it's totally fine.

For the sake of this discussion, here's an example of what I was trying to say (probably should've done it on the initial post, oh well, here it goes..):

  • productPageRoute.vue - takes productName as a route parameter.
<template>
  <h1>{{ product.name }}</h1>
  <h2>{{ product.shortDescription }}</h2>
  <table>
    <tr><td>Date</td><td>Total</td></tr>
    <tr v-for="o in getProductOrderHistory(product.id)">
      <td>{{ o.date }}</td><td>{{ o.total }}</td>
    </tr>
  </table>
</template>

<script>
export default {
  data() {
    return {
      product: this.getProductByName(this.$route.params.productName)
  },
  vuex: {
    getters: {
      getProductByName: (state, productName) => {
        for (let p in state.products) {
          if (p.name === productName) {
            return p;
          }
        }
      },
      getProductOrderHistory: (state, productId) => {
        // return state.productsOrdersHistory array filtered by productId
      }
    }
  }
}
</script>

Obviously that in this example everything would be much simpler if the route just gets the productId as a route parameter, but that's not the point for this discussion.

It would be very good if we could read the state in a similar way that I showed on the example. I think it would make Vuex more pleasant to work with.

I know you're right @yyx990803 when you say that the argument needs to come from somewhere and that it can be in the state, since it is actually state of the app! But what if, as an example, there's something that needs to read the state (and it updates the state to achieve that) but it sits inside a v-for? Would this mean that we should update the respective state for each v-for iteration?

Thanks again!

I'd say the naming convention is slightly confusing, getters indicates state can be retrieved in any form, but in fact they are reducers.

Perhaps we should have reducers being pure methods. Which can be used for filtering, mapping ect.

getters then can be given any context. Similar to computed, but you can now combine computed props to getters in vuex option. Which helps structure of components.

This would create more flexibility for these scenarios.

Hi,

If I understood well, in case we need a getter to feed a table, it will have to return the corresponding object to this table and than we need to build a v-for with this object in the template.

In same way, If we have more than one table to be chosen by the user, the getter would have to return the corresponding object to all tables.

Wouldn't be more flexible if the getters accept, in addition to state, arguments that serve to choose exactly which state's slice we want. These arguments could be the indice generated by a v-for or selecting an item from a list by the user.

In 2.0, getters are now defined in the store. To make them efficiently cachable, 2.0 getters must be pure too.

Also for the original request, the more appropriate approach is storing the current route state in the store as well. This way the getters has access to that information too.

If a component needs derived store state based on its own state, it should define a local computed property.

Does vuex getter have to be state => state.foo instead of (state, id) => state.foo[id]?
Is it possible for me to pass an extra argument to the getter?

When trying to pass extra argument I got the following:

[Vue warn]: Error when evaluating expression "function ping() {
            this.printer['site'] = this.site.id;
            return this.printerPing(this.printer);
        }": TypeError: this.printerPing is not a function (found in component: <printer>)

Getters

retrievePrinterPing(state, printer) {
    return state.pings[printer.site][printer.hostname];
}

Computed value

    computed: {
        ping() {
            this.printer['site'] = this.site.id;
            return this.printerPing(this.printer);
        }
    }

Vuex

getters: {
    pings: getters.retrievePings,
    printerPing: getters.retrievePrinterPing
}

Question

Any suggestion?

If we want to allow extra arguments to getters, we can define the getter returning the function that receives extra arguments.

retrievePrinterPing: state => printer => {
  return state.pings[printer.site][printer.hostname];
}

@yyx990803 When using modules you can't get the route state inside the getter. For me, this is a MUST HAVE feature (allowing getters to have arguments).

@deiucanta, there's a native Vue plugin for what you need:

vuex-router-sync puts route information into the vuex store.
You can then reduce the state of the store in your components getters.

@ktsn Is returning a function from a getter considered an anti pattern? It feels kinda hacky for something that I need to do ALOT. E.g.getProductById, getSubscriptionById. I could do it asynchronously by returning a promise from an action, but in many cases I just need a method that synchronously filters data I already have in the store. I like having methods that filter the state tree available on the store so I can easily map them to components. This way I avoid having to import specific filtering methods or mixins in my components.

Is returning a function from a getter considered an anti pattern?

Are you asking if a core team member knowingly advises to use an anti-pattern? That would be strange, would it not? :D

But it's not something you should have to do a lot.

It feels kinda hacky for something that I need to do ALOT

I often find a need to do this when I have local state in a component that should rather be in the store.

Could some of those cases be solved by having e.g. the currentProductId in the store?

Another recommended pattern would be to organize your data in objects instead of with ids as the keys:

products: {
  13445: { ... ,}
  75765: { ... },
  .... 
}

Then there is no need for a filter function, you can simply do this.$store.state.products[this.id] in your component. Also more performant than filtering an array each time.

If you also need to track the order of the items, save it in a separate array with only ids:

productsOrder: [75765, 13445, ...]

if you need an ordered array of products, that's a great usecase for a getter:

productsOrdered: ({products, productsOrder}) => {
  return productsOrder.map(id => products[id])
}

(...and now I regret choosing "products" for the example, because with products, "order" is ambiguous. Hope you get the point.)

@LinusBorg I've seen the recommendation of putting a currentProductId in the store, but that's not semantically correct in any of my use cases. I'm more or less just trying to make centralised filtering easy. You're right that i could mapState if my data was keyed by ids, but that's not the case for me and Vuex shouldn't assume something like that anyway. I've felt the need for something like what @ktsn recommended for a long time, and it works perfectly, but I haven't seen anyone else talk about it, so that's why I was wondering if it's a recommended practise.

You're right that i could mapState if my data was keyed by ids, but that's not the case for me and Vuex shouldn't assume something like that anyway.

Vuex doesn't. But it's normal that certain data structures work better for certain scenarios (with or without vuex), it'S always a trade-off. I was only trying to provide a hint about how to strcutre your data more effectively if that is within your means.

@wolthers
I don't think it's an anti-pattern. Also I'm using this pattern on my project and it works well.
This pattern would be the simplest solution to pass some arguments to getters. If we naively introduce argument in getters, the argument list is too bloated (or breaking change is needed) while the approach can omit unused getter arguments.

// add user defined argument to tail...
someGetter: (state, getters, rootState, rootGetters, arg1, arg2) => { /* ... */ }

// be similar to action interface but having breaking change...
someGetter: ({ state }, arg1, arg2) => { /* ... */ }

// returning function
someGetter: state => (arg1, arg2) => { /* ... */ }

P.S. returning function is normal in functional programming :)

@LinusBorg Fair enough, I was only trying to point out that I don't see any official documentation on what I'm trying to achieve, which I thought was odd considering the fact that - at least in my view -it's a super common use case. But thanks for taking the time to answer, I appreciate it @LinusBorg @ktsn

If something was going to be changed to address these not-uncommon scenarios, why shouldn't it be _mapGetters_? Isn't the whole point of that helper to map computed properties to store getters? And isn't this use case a slight variation of just that?

This just tripped me up as a beginner, I saw this code in a tutorial:

export const getProductById = state => (id) => {
    return state.all.find(product => product.id == id)
}
computed: {
    product(){
      return this.$store.getters.getProductById(this.id)
    }
}

And I immediately thought, hang on, aren't we supposed to mapGetters like it says in the docs, why is this computed property accessing the store differently? Then I found this thread.

Is this an application design problem on my (and the tutorials) part?

It was mentioned to store the currentProductId in the store. Which would mean, grabbing the id from the URL, putting the correct product in the store, then pulling it out again?

Or should is the above fine and and just an exception to using MapGetters when needing to pass arguements?

It's totally possible to return a function from a getter, which is basically what you tutorial did.

If the id is in the URL though, I would probably use vuex-router-sync. This syncs route parameters to the store, so we can access them in getters.

I need a single place to store my utility functions. Passing args to a getter would work but its a hack. Why not provide a Utility object off the store?

Why not just put them into a normal JS module, and import them where you need them? You can even add them to each instance with the strategies described here: https://vuejs.org/v2/guide/plugins.html

Vuex is about state, I think it should not be (ab)used as a dependency injection tool (which is what your approach would do afaik)

2.0 getters must be pure too.

What does this actually mean, for a getter to be pure?

That the functions don't have any side-effects. given the same arguments, they should always return the same result, and only return that result (i.e. not mutate data somewhere else etc.)

@LinusBorg wouldn't it just be easier to provide a helper that allows getters that return functions to be mapped to methods? Maybe it is just me but syncing the router state to the store feels a bit awkward as a solution here.

Was this page helpful?
0 / 5 - 0 ratings