Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2.0 design #236

Closed
yyx990803 opened this issue Jul 1, 2016 · 65 comments
Closed

2.0 design #236

yyx990803 opened this issue Jul 1, 2016 · 65 comments

Comments

@yyx990803
Copy link
Member

yyx990803 commented Jul 1, 2016

Note: this proposal has been heavily revised based on feedback so some of the comments below may be out of context.

1. Terms naming change for better semantics

Dispatching a mutation never sounded right. Dispatch should be indicating the intention for something to happen. For mutations, we want a verb that indicates the state change is happening as soon as you call it.

  • The old store.dispatch is now store.commit.
  • store.dispatch will be used for firing actions instead. (see next section)

The new naming better conveys the semantics behind the two methods:

  • "dispatching an action": indicating the intention for something to happen (possibly async with side effects).
  • "committing a mutation": indicating the synchronous transaction of actual state change.

2. Module Portability & Composability

Actions inside store and modules

There has been common requests on shipping actions with the store, or inside modules. Previously, the reason for not putting actions in the store/modules was mainly how do we access them. Actions defined inside the store means we need access to the store - again the singleton problem, which is now a non-issue.

Now since they are just functions, we may expose them as store.actions and call them directly. In fact this was the API of Vuex 0.4.x:

store.xxx // reserved for Store class methods/properties
store.actions.xxx // this seems ok

A problem is when actions are defined inside modules, what if multiple modules define actions of the same name? Also, sometimes we may want to call an action that affect multiple stores, just like mutations.

It seems actions are also more like event listeners. Instead of calling them directly as functions, we now use store.dispatch to trigger them:

const store = new Vuex.Store({
  actions: {
    doSomething: ({ commit }) => {
      commit('some-mutation')
    }
  }
})

store.dispatch('doSomething')

This way, you can dispatch actions in multiple modules with a single call, just like mutations. It's also more explicit that you are firing off some side-effects in your store, instead of just calling a random function.

Getters, too

You can now define getters in the store / modules too. Similar to module mutations, module getters receive the sub state tree:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    hasAny: state => state.count > 0
  }
})

// access the getter
store.getters.hasAny // -> false

3. Composable Action Flow

Now that we are putting actions inside modules and calling them via dispatch, we somehow lose out on the composability of actions, because they are no longer just functions you can call. Here's how we can make them composable again:

  1. To indicate the completion of an action, return a Promise from the action. store.dispatch will return that Promise if there is only a single handler called. If multiple action handlers are matched, it will return a Promise that resolves when all Promises returned by those handlers are resolved.

    const store = new Vuex.Store({
      actions: {
        doSomething: ({ commit }, payload) => {
          return callPromiseAPI(payload).then(res => {
             commit('some-mutation', { res })
          })
        }
      }
    })
    
    store.dispatch('doSomething', { id: 1 }).then(() => {
      // action done
    })
  2. Based on (1) and async/await, we can have very clean composition between async actions:

    const store = new Vuex.Store({
      actions: {
        one: async ({ commit }, payload) => {
          const res = await callPromiseAPI(payload)
          commit('some-mutation', { res })
        },
        two: async ({ dispatch, commit }) => {
          await dispatch('one')
          commit('done')
        }
      }
    })
    
    store.dispatch('two') // fires off complicated async flow

    The convention of returning Promises also allows Vuex to:

    1. better handle errors during async action flow.
    2. simplify store initialization during server-side rendering.

Component Binding

The design of Vuex 0.6~1.0 had a somewhat annoying design constraint: avoid directly accessing stores inside components. The store is injected at the root component, and implicitly used via the vuex: { getters, actions } options. This was in preparation for Vue 2.0 SSR (server-side rendering), because in common SSR setups (directly requiring the component in Node.js and render it), dependence on a global singleton will cause that singleton to be shared across multiple requests, thus making it possible for a request to pollute the state of the next one.

However, with the new bundleRenderer strategy implemented in Vue 2.0.0-alpha.7, this is no longer an issue. The application bundle will be run in a new context for each request, making it unnecessary to structure your app without singletons just for the sake SSR. This also means it's totally fine to just import store from './store', use plain computed properties to return store.state.xxx, or calling store.dispatch() in plain methods.

This opens up path to simplifying the component binding usage, since theoretically you don't need any binding at all. Currently, the vuex options feels a bit clumsy and indirect.

In Vuex 2.0, the vuex option will be deprecated in favor of just computed properties and methods. You are free to structure your Vuex store usage the way you prefer. However, we will be keeping the injection for this.$store so that you can do this:

export default {
  computed: {
    a () {
      return this.$store.getters.a
    }
  },
  methods: {
    b (...args) {
      this.$store.dispatch('b', …args)
    }
  }
}

The above alleviates the need to import the store everywhere. But it can get verbose when you have many getters and actions in the same component. Therefore we provide two helpers, mapGetters and mapActions:

import { mapGetters, mapActions } from 'vuex'

export default {
  computed: mapGetters(['a', 'b', 'c']),
  methods: mapActions(['d', 'e', 'f'])
}

So in the component, this.a maps to this.$store.getters.a, and this.d(...args) maps to this.$store.dispatch('d', ...args).

If you want to map a getter/action to a different local name, use an object instead:

import { mapGetters, mapActions } from 'vuex'

export default {
  computed: mapGetters({
    myComputed: 'a' // map this.myComputed to store.getters.a
  }),
  methods: mapActions({
    myMethod: 'b' // map this.myMethod() to store.dispatch('b')
  })
}

Finally, you can easily compose them with local computed properties and methods using Object spread operator:

import { mapGetters, mapActions } from 'vuex'

export default {
  computed: {
    localComputed () {  },
    ...mapGetters(['a', 'b', 'c', 'd'])
  },
  methods: {
    localMethod () {  },
    ...mapActions(['b'])
  }
}

2.0 so soon? How about 1.0?

1.0 contains small breaking changes but should be a very easy upgrade for existing 0.6~0.8 users. It will be maintained as a stable release in parallel to 2.0.

@nicolasparada
Copy link

nicolasparada commented Jul 1, 2016

My thoughts are on the order of params inside actions.
I mean:

const store = new Vuex.Store({
  actions: {
    doSomething(state, payload, dispatch) {
      dispatch('some-mutation', payload)
    }
  }
})

...could be the in the inverse order:

const store = new Vuex.Store({
  actions: {
    doSomething(dispatch, payload) {
      dispatch('some-mutation', payload)
    }
  }
})

Because, inside an action you always use dispatch, then, if there is a payload, you use it.

@yyx990803
Copy link
Member Author

@nicolasparada definitely, the argument orders are not set in stone - I actually was thinking about it too.

@ghost
Copy link

ghost commented Jul 1, 2016

how about dynamic modules injection?
like store start with a basic set of modules then user can fetch and inject new ones.
and presumably delete them when no more needed.

@sebastiandedeyne
Copy link
Contributor

sebastiandedeyne commented Jul 1, 2016

This opens up path to further simplifying the component binding usage. Currently I'm interested to hear thoughts on the current binding (aka vuex: { getters, actions }).

This might have been discussed at some point, but is the vuex key necessary? I'd prefer to declare getters and actions on the top level of my component.

Kind of wish there was some way to make getters and actions play nicely with props (from a syntax point of view, having to declare getters and computed props for one thing feels like a hassle sometimes), but don't have any ideas myself.

store.dispatch('counter:incrementAsync')

I'm not sure about namespacing actions. Wouldn't that restrict you from using modules if you want a single action that can trigger multiple mutations?

@blake-newman
Copy link
Member

blake-newman commented Jul 1, 2016

Mainly, I agree with all the concepts and ideas brought in by this design. It will certainly help further simplify Vuex.

This opens up path to further simplifying the component binding usage. Currently I'm interested to hear thoughts on the current binding (aka vuex: { getters, actions }).

The vuex property key, in the current architecture is kind of necessary. For 2.0 I would consider it very unnecessary:

  • Computed properties and methods are learn't in Vue.
    • Current Vuex we are adding another abstraction which is essentially thesamee thing as methods and computed properties.
    • This adds an additional learning curve.
  • Vuex introduces nested object to 3
    • Looks and feels ugly
    • Other nesting doesn't go above 2 computed -> computedProperty

Actions and mutations are now together "events". The difference being that

  • mutations are synchronous and can mutate the state
  • actions can be async, cannot directly mutate the state (but can trigger mutations and other actions)

I would replace store.dispatch with equivalent store.action and store.mutate.

  • Helps isolate the concepts through the API.
  • Learning Vuex includes have to learn the flux principles, therefore dispatch becomes something additional to digest that does not fit in with what is learnt through Flux.
  • The naming mutate and action fits nicely into the concepts learnt from Flux.
  • This may also help with slight performance gains as we are splitting of the target for the events

In agreement with @nicolasparada, this makes simple actions easier to create.

@ruiposse
Copy link

ruiposse commented Jul 1, 2016

I agree with @blake-newman that store.action and store.mutate is better than store.dispatch.
I would also add that is more readable because we can immediately understand what happening on the store.

@simplesmiler
Copy link
Member

simplesmiler commented Jul 1, 2016

Interesting, quite a step away from Redux.

Redux community tried to bundle action creators with reducers, but I believe Dan is against it (1, 2). According to him, it kills the essence of flux: action objects and reducers are separated, any reducer can respond to any action object. That said, bundled modules are optional, so you can use then when they fit and fall back to idiomatic Redux when they don't.

To be honest, I don't like the idea of dispatching actions and mutations through a single method. I think that everything that is dispatched should be recorded by the devtools. But actions should not be recorded because of side effects. In Redux you can dispatch anything, and "non-idiomatic" chunks are processed by middleware. But it's quite confusing, I'd rather have separate methods (mutate and act?)


Onto suggestions.

With dispatch now performing prefixing (and thus being unable to affect parent modules), how about making actions take a module, which has it's local versions of module.dispatch and module.state? This would simplify action signature from state, payload, dispatch, [done] to module, payload, [done]. We can even keep calling it store, but it will change semantincs to be a "local store, aka module".


UPDATE: Added references to Dan's words:

@yyx990803
Copy link
Member Author

@simplesmiler I think there is a real need for actions defined alongside mutations, which doesn't prevent a higher-up action from dispatching multiple namespaced mutations.

I like the arguments simplification - it also keeps it closer to the current API, so the signature remains almost he same:

  • mutations: (state, payload) => void
  • actions: ({ dispatch, state }, payload, [done]) => any

@yyx990803
Copy link
Member Author

@sleewoo that's something I've been thinking too! It could also be used for hot-reloading a module. Maybe something like store.module(id, { ... }).

@yyx990803
Copy link
Member Author

yyx990803 commented Jul 1, 2016

@sebastiandedeyne I also feel the nesting is a bit awkward. In 2.0 we may get rid of the options all together, so you can just do this.$store.dispatch() in normal methods + return this.$store.state.xxx in computed properties.

If you want a single action to trigger multiple mutations, you just need to be more explicit:

dispatch('moduleA:increment')
dispatch('moduleB:increment')

I think this actually makes the data flow clearer: you know you are triggering mutations in two different modules and what modules are affected.

We may also need an "escape hatch" for dispatching mutations in root scope from any module:

dispatch('@someGlobalMutation')
dispatch('@rootModule:mutation')

And a module can listen to global mutations using the same syntax:

mutations: {
  '@someGlobalMutation': state => {
    // state is still local state
  }
}

@yyx990803
Copy link
Member Author

As for separating methods for mutations and actions - this is also something I thought about. Maybe we keep dispatch (since it's already been used with mutations) and use call for actions. action itself is not a verb so it feels weird. call seems to signify the "side-effect-ness" of actions pretty well.

@sebastiandedeyne
Copy link
Contributor

Feels kind of weird though, all other libraries teach us to dispatch actions, and then Vuex would call actions and dispatch mutations.

I agree with the separation, just not with the naming.

@simplesmiler
Copy link
Member

simplesmiler commented Jul 1, 2016

We may also need an "escape hatch" for dispatching mutations in root scope from any module:

dispatch('@someRootMutation')
dispatch('@rootModule:mutation')

That would be nice. Maybe even consider them global (or application-wide) instead of root-scoped, and let any module issue and react to global mutations and actions.


UPDATE: or even invert the logic, and make "normal" mutations global, and @-mutations local. This will basically keep things compatible with current Vuex.

@simplesmiler
Copy link
Member

Also, should new Vuex encourage state: () => <state> instead of state: <state>, now that modules will be essentially reusable?

@OEvgeny
Copy link

OEvgeny commented Jul 1, 2016

Keeping actions to work with only one module/mutation sounds a bit wired to me. Imagine I need to implement some module to collect stats for my app. With redux I can just attach reducer to my store and make it respond to actions I need. With Vuex I forced to be more explicit and duplicate dispatch calls of needed actions to perform additional mutations.

@ktsn
Copy link
Member

ktsn commented Jul 1, 2016

I think the design concept is very reasonable. In my developments, I always prefix or suffix mutation names for a related module. (and also I do it on Redux)

And I also agree with @simplesmiler. Explicitly dispatching multiple mutations is good, but I think sometimes we want to dispatch some mutations globally.
For example, imagine an app that can search some complex data and show various visualization on one page. I want to reset all state of previous search for each search query submission.

In my opinion, global registration would be better than global dispatch because global dispatch can accidentally execute unexpected mutations.

// normal mutation name
const RESET = 'reset'

// global mutation name
// e.g. prefixed with special character is global
const GLOBAL_RESET = '#reset'

const someModule = {
  state: { value: '' },
  mutations: {
    [RESET] (state) { // register under the namespace
      state.value = ''
    }

    [GLOBAL_RESET] (state) { // register to global
      state.value = ''
    }
  }
}

// RESET is namespaced
store.dispatch(`someModule:${RESET}`)

// GLOBAL_RESET is not namespaced
store.dispatch(GLOBAL_RESET)

@kazupon
Copy link
Member

kazupon commented Jul 1, 2016

I basically agree all the concept and ideas and @blake-newman. And I think that we should be provided the useful Vuex API I/F for user.

@yyx990803
Copy link
Member Author

@OEvgeny even in Redux it feels weird using a reducer for collecting app analytics - a middleware seems more appropriate. So in Vuex you'd just write a plugin for that purpose.

@OEvgeny
Copy link

OEvgeny commented Jul 1, 2016

@yyx990803 thanks for response. I think there are different types of stats that can be collected. Some of them we don't need to process on the server side, some we need to collect before processing, and some we will use to show in featured parts of our app.

I like what with redux we can continue changing our store logic without touching whole app. As an example we can rewrite some parts of our store to fit new needs and implement new components/views for our new state without breaking or modifying our actions and current components/views. We can mix up together new and old parts of our store until it's needed. So, it gives more flexibility for further development.

@yyx990803
Copy link
Member Author

@OEvgeny I don't think namespaced modules would make it any harder - I'm not sure what aspects in the proposal makes you think that way?

@OEvgeny
Copy link

OEvgeny commented Jul 2, 2016

@yyx990803 I think it's all about forcing mutations inside modules to be namespaced. Then each time when I change structure of my store, I will be forced to change actions to fit this changes. I think it can be hard to do in some cases.
As an example redux also gives the ability to namespace actions without forcing to do it. It allows developer to decide not only how store organized but how actions will affect it.

@yyx990803
Copy link
Member Author

yyx990803 commented Jul 2, 2016

@OEvgeny

  1. Each time you change the structure of your redux store, you will also
    need to change all the mapStateToProps connections that are affected. Also
    any action creators that uses getState() are affected too. If your state
    shape changed, you always have some work to do.
  2. Namespacing is not forced. If you want you can still use global
    mutations inside modules, which is basically the same behavior as now.

@simplesmiler
Copy link
Member

With an option to operate on global scope you have my thumbs up.

@OEvgeny
Copy link

OEvgeny commented Jul 2, 2016

@yyx990803

  1. Yes you are right. Usually we need to change mapStateToProps and some actions which use getState(). But there are cases when it is not needed. For example we can add new reducer to our store and get it to work with existing actions while we are working on some new features/components and migrate existing components which rely on "old" part of state later.
  2. Good to know. It was not clear from first message:

Mutations inside modules become namespaced

I'm agree with @simplesmiler about some option to achieve global actions behavior.

@yyx990803
Copy link
Member Author

Actually, now that I think about it, there's no need for auto-namespacing, because users can namespace the mutations/actions themselves. This does leave the potential for namespace clashes, but the chance seems quite low. All we really need is auto state-resolving for module actions.

@yyx990803
Copy link
Member Author

For all those interested, the proposal has been heavily revised. Feedback welcome.

@jonjrodriguez
Copy link

I love everything about the proposal and new features and will be moving to this version as soon as I get some time to refactor.

My only question is regarding module getters receiving the sub state tree: I currently normalize all my api requests and put the different entities in different modules. This will cause all my getters to live on the root store when I want to denormalize an item. Any reason why the module getters can't receive the full state? Is it just to align with the module mutations?

@yyx990803
Copy link
Member Author

yyx990803 commented Jul 11, 2016

@theotherzach long snippets are more appropriate in a gist, and a proposal, especially one that directly contradicts this design, would be more fitting as a separate issue.

That said, I think your usage (from my memory of glancing over what you posted before) were indeed missing a few points about Vuex:

  1. As you've noticed, managing state through Vuex does add additional boilerplate, so you need to be aware of the tradeoff. The benefits of Vuex is that changes going through the store are trackable, replayable and restorable. This is particularly useful when combined with the devtool to help you understand what is going on with your application state. The design is also focused on providing a scalable structure for large applications with multiple teams - the "boilerplate" part is there to ensure everyone with knowledge of how Vuex works knows where to look for things when they jump into a Vuex-based app.

  2. It's not mandatory, nor recommended to put all your state inside a Vuex store. The examples are probably to be blamed because they are contrived and in fact too trivial to require using Vuex (they just examples). The guide mentions upfront that it's important to distinguish between application-level state and component local state. If you are not sure whether a piece of state belongs to a component or the store, start with local state. For example, a temporary flag like isLoadingUser should most likely be component local state. In most cases, you'd extract state into the store for two purposes:

    1. You realize it needs to be shared among multiple components in the entire application.
    2. It is important enough that you want to require dispatching actions to change it, so that you can track it in devtools.

    If you simply put every single property in a big component into Vuex, you will most likely end up with more code and no obvious gain.

  3. Getters are not mandatory. In fact their purpose is for computing derived state. Nothing prevents you from directly accessing store state, even in templates you can do {{ $store.state.someData }}.

  4. The distinction between mutations and actions is important. An action stand for a possibly asynchronous sequence of operations, while mutations stand for a synchronous, trackable transaction. The guide already explained this.

Finally, if you don't think it's worth the extra code to gain trackability of your state (as shown from your proposal), you are probably better off just stay with component local state + the simple store pattern mentioned here.

@theotherzach
Copy link

@posva Sorry for my over-reaction but I'm late on this project and exhausted. In short I find the following based on my experiences. I hope that I'm using Vuex wrong, but even if that's the case then perhaps this points to evidence that Vuex is easy to use wrong.

  • Vuex is great for me when I know ahead of time that I've got enough state to warrant it and I don't have too many async calls.
  • The more async calls I have, the more mutation & getter boilerplate I write.
    • Loading indicators
    • recovering after a network call failed after I optimistically updated the UI, such as re-opening a previously closed address form and highlighting the fields with errors.
  • I find it very difficult to keep state in both components and in the vuex store.

https://gist.github.com/theotherzach/92ddb73af8f9e4c8ab70ca82a49c8fc1

@yyx990803
Copy link
Member Author

@thoughts1053 I think it's probably a good idea to expose an extra rootState for module getters/mutations/actions.

@jonjrodriguez
Copy link

@yyx990803, hopefully last questions regarding getters:

  1. Are getters able to currently call other getters from different modules? - I guess we can just export the getter and manually import in in another module.
  2. Can getters take arguments?
  • Regarding denormalized data (ie, posts and comments) and using getters from different modules, this would allow us to do something like getCommentsForPost(postId).
  • Regarding performance (and this may not be an issue), currently I have to get a list of posts (keeping with the above example) and pass each post as a prop into a component. When updating a single post in the list, is the list of posts retrieved again and then each component re-rendered? If so, wouldn't having the component have a getter getPost(postId) prevent unnecessary re-renders of other components?

@piknik
Copy link

piknik commented Jul 11, 2016

@thoughts1053 You hit the nail on the head on both points. I find myself doing 1 a lot, and 2 would make life so much easier.

Currently in 1.0 when you call a getter from a getter, it requires passing in the state/store to the next getter, and putting each getter into an object that can be accessed by all getters. Would be nice to give each getter some way of calling another getter like how you would in a component this.cartItems vs getters.cartItems(store). Even nicer to provide arguments, example this.cartItems(accountId).

@richard-engineering
Copy link

@yyx990803
I think it would be helpful to have default mutators/actions that perform a "set" behavior and thus not require explicit boilerplate declarations. Having to write custom get/set code tends to be the exception, not the rule, imho. This provides the benefits of track ability while reducing the amount of code needed.

@LinusBorg
Copy link
Member

LinusBorg commented Jul 12, 2016

@thoughts1053

Concerning 1.:
That is now possible with 2.0.0.-rc3, and geters not also have access to the rootState in modules.

Concerning 2.:
I don't think this in nessessary. To me, there are two scenarios where a parameter might seem to be useful, but is not:

  1. you have some local state and want to filter depending on that. in that case, you could simply use a computed property to get what you want from this.$store.

    Getters should be pure functions to be re-usable independently from component state, so they cannot access local state like a compute prop in a component can.

    A getter in the store would make not much sense to me anyway, as the operation is only meant for that component anyway.

  2. You have a collection in the store (i.e. comments) as well as the filter criterion; currentPost. In that situation you don't need to receive the currentPost as a parameter because it is already available in the store. Especially sice now we can use other getters in a getter, this should provide more than enough flexibility.

@jonjrodriguez
Copy link

@LinusBorg Thanks! I saw the release last night.

You've also answered both my questions regarding passing parameters to getters. #1 was my main concern, but creating a local computed property with this.$store makes sense. Thanks again.

@LinusBorg
Copy link
Member

LinusBorg commented Jul 12, 2016

@theotherzach

Looking at the gist you posted, that should probably be split up into some state submodules to make it more maintanable.

concerning the boilerplate when having async actions: The 2.0 design proposal actually makes this much easier since async actions now should return a Promise:

// in a component's method:
this.loading = true
store.dispatch('doSomething', { id: 1 })
.then(function (result) {
  this.loading = false
})
.catch(function(err){
  this.loading = false
  // call this async action again, and // or handle error display ...
})

@analog-nico
Copy link

I just upgraded to vuex@2.0.0-rc.3 and as a result have two small suggestions:

  1. For convenience mapActions and mapGetters should map ALL available actions/getters if they are called without any parameters.
  2. Suggesting to use mapActions and mapGetters with the Object spread operator is very neat but I only realized that the Object spread operator is part of the ES7 draft spec after I ran into many issues (eslint does not support it by default, buble requires a polyfill). I suggest adding the following sentence to the documentation: "The Object spread operator is part of the ES7 specification. Please verify that your toolchain is properly configured to support this operator."

@karol-f
Copy link

karol-f commented Jul 14, 2016

@analog-nico about 2). I only had to use this - https://babeljs.io/docs/plugins/transform-object-rest-spread/. No other setup was needed in my case. I agree however about the need of info in documentation.

@nicolasparada
Copy link

nicolasparada commented Jul 15, 2016

I didn't test it yet, so... does Object.assign() work?

computed: Object.assign({}, 
  localComputed() {/*...*/},
  mapGetters(['a', 'b', 'c', 'd'])
),

methods: Object.assign({},
  localMethod() {/*...*/},
  mapActions(['b'])
)

??

@analog-nico
Copy link

@karol-f True, as you say, the note in the docs is exactly my point. Likely any toolchain will easily support the operator. But since it is ES7, chances are, it's not supported by default. So it is better having the note than letting more people like me turning their toolchain inside out. ;)

@nicolasparada Object.assign is a good ES6 alternative. Your code works if you add curly braces around localComputed and localMethod. However, Object.assign needs a polyfill to run in ES5 environments. Babel likely adds one automatically. For buble you have to add it yourself.

@nicolasparada
Copy link

@analog-nico Ah, of course, I forgot them.

@Namek
Copy link

Namek commented Sep 25, 2016

@yyx990803 what's the recommended way regarding getters with parameters in Vuex 2.0? I used them as helpers, e.g. for some calendar I have v-for looping through days and getting them using getDay() helper as such:

function getDayIndex(weekIndex, weekDayIndex) {
  return weekIndex*c.WEEK_DAY_COUNT + weekDayIndex
}

export const getDay = ({calendar}) => (weekIndex, weekDayIndex) => {
  return calendar.days[getDayIndex(weekIndex, weekDayIndex)]
}

@ronwong82
Copy link

@yyx990803 I get error dispatch undefined. FYI I'm using vuex 2.3. what's wrong?

@LinusBorg
Copy link
Member

This is not a support forum. Please ask questions on forum.vuejs.org

Also, please provide code that we and other people on the forum can analyse to solve your problem.

@vuejs vuejs locked and limited conversation to collaborators Jun 16, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests