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

Proposal: Global action enhancers - WIP #929

Closed
wants to merge 1 commit into from

Conversation

deini
Copy link
Contributor

@deini deini commented Sep 4, 2017

Proposal to bring Global action enhancers.

I wanted a way to log all actions and their payload when dispatched. Following the discussion in #908 seems like there is no way right now to do cleanly.

The proposal is to bring global action enhancers that work just like redux's middleware.

Using the count example and some inline actionEnhancers:

export default new Vuex.Store({
  state,
  getters,
  actions,
  mutations,
  actionEnhancers: [
    context => next => (action, payload) => {
      console.log('Hello from first enhancer:', context.state.count)
      return next(action, payload)
    },
    context => next => (action, payload) => {
      console.log('Hello from second enhancer:', context.state.count)
      return next(action, payload)
    }
  ]
})

If we can get something like this, then adding an actions tab in devtools would be pretty straight forward.

TODO:

  • Feedback
  • Docs
  • Tests

@ktsn
Copy link
Member

ktsn commented Sep 14, 2017

Hi @deini. Thanks for your contribution!
I just read #908 and I think the action tab in devtools is very nice idea 👍

One thing I'm concerning is that global action enhancer seems a bit overkill to me. It potentially allows users to mutate all the dispatched actions and can complicates the actions flow.

As we just want to listen the dispatch, wouldn't it enough to add subscribe-like method but for action? It also have advantage that it can be utilized in some plugins.

function actionLoggerPlugin(store) {
  // Not sure how we should call this method...
  store.subscribeAction(({ type, payload }, state) => {
    console.log(`Before dispatching "${type}" with ${JSON.stringify(payload)}`
  })
}

const store = new Vuex.Store({
  // ...
  plugins: [actionLoggerPlugin]
})

@deini
Copy link
Contributor Author

deini commented Sep 14, 2017

@ktsn I agree, global action enhancers are overkill for #908, however it could open up for more middleware use cases. It indeed complicates things more and you are right that a user can mutate / discard actions.

I like your proposal, as its all we need for #908. Not sure if you guys had any other use cases for GAE, but if not, feel free to close and I can give subscribeAction a shot.

@yyx990803
Copy link
Member

subscribeAction sounds good to me 👍

@LinusBorg
Copy link
Member

LinusBorg commented Sep 20, 2017

I'm currently playing around with the ideas from redux-first-router and try to come up with a Vue&Vuex solution to see if that could be a useful thing (it seems like a great fit for SSR, for example).

This pattern uses actions as triggers for route navigation, so these proposed actionEnhancers would actually be perfect to implement navigation guards, redirects etc. Currently I monkeypatch dispatch() to be able to do that.

a subscribeAction as suggested by @ktsn would not work here, as I would not have any way of cancelling the action.

@ktsn
Copy link
Member

ktsn commented Sep 21, 2017

@LinusBorg Thanks for the idea! I didn't know redux-first-router but it looks nice concept 👍 . I agree that it would be also useful in Vue & Vuex.

I'm thinking how we can implement that concept in Vuex and it could be solved without enhancer/middleware feature. PoC is https://github.com/ktsn/vuex-first-router
The basic idea is to separate the actions with route change request and route changed event.

For example, if we have foo route and want to move to it, we dispatch foo action (route change request). Then the router changes the url and dispatches another action - applied:foo action (route changed event).
If foo actually redirects to bar, we dispatch foo action but applied:bar will be dispatched after that.

I'm not sure if we need to use the same action name to achieve this concept. But to my understanding, the implementation would be simpler by separating the actions because we don't need to branch the further processing where the action comes from (changing url on address bar or directly dispatching it).

Maybe I'm a bit harsh with this topic 😅 . But I'd say the action already can do various things out of the box, not sure the trade off between the benefit and additional complexity.

@lmiller1990
Copy link
Member

lmiller1990 commented Nov 2, 2017

I initially asked on stackoverflow, but I am looking for to trigger various mutations when actions are dispatched.

subscribeAction sounds like exactly what I'm looking for. My current solution is to wrap actions in an enhancer, like vuexfire and some other plugins do, but I think this functionality would be nice to have in core. I see the potential for additional, unwarranted complexity as well though.

Any update on this PR, or anything I can do to help out?

@jannesiera
Copy link

Any progress on this?

I'm currently looking for a way to :

  1. automatically call mutations if an action of the same name doesn't exsist (so my calling code always dispatches, never commits).
  2. prevent commits from being accepted with a decoupled guard function.

I got this to work to overwrite vuex's dispatch and commit methods. However, it would be nice if I could achieve both in a supported and standardized way.

@jannesiera
Copy link

Bump.

Ran into another use case where this would be very useful. While creating a small ORM to handle mutations of the state with all internal relations in a standard way, it would be very handy if I could pass the state to the ORM manager before every mutation.

Currently, I'd have to either (1) call the function at the beginning of every mutation manually or (2) overwrite the dispatch/commit methods again.

Option 1 is an unacceptable amount of boilerplate that I couldn't afford in any professional project.
Option 2 will make it virtually impossible to share my solution with the wider Vuex community.

@lmiller1990
Copy link
Member

lmiller1990 commented May 21, 2018

I posted earlier asking for a way to subscribe to actions which was indeed added in 2.5 - subscribeAction.

@jannesiera couldn't you write a plugin that just hooks into store.subscribeAction to achieve the goal you want?

function plugin() {
  return store => {
    store.subscribeAction(action, state) => {
      // do stuff
    }
  }
}

new Vuex.Store({
  plugins: [plugin()]
})

Something along those lines.

@zgayjjf
Copy link

zgayjjf commented Jul 30, 2018

@lmiller1990
subscribeAction can only executed after action executed.
What @jannesiera want was something executed before action, which I think would be great if there is someway.

@ta4
Copy link

ta4 commented Aug 13, 2018

How we can cancel action or modify payload inside store.subscribeAction hook?

@Gvade
Copy link

Gvade commented Sep 17, 2018

@jannesiera
In order to write some sort of middleware for your actions or mutations you can use the standard ES2015 Proxy. For instance:

...
const actions = {
    close(context, payload) {
       //your action
    }
}

actions['close'] = new Proxy(actions.close, {
    apply: function(target, thisArg, argumentsList) {
        const [context, payload] = argumentsList;
        //your middleware code
        return target.apply(thisArg, argumentsList);
    }
});
...

But if I understand correctly, the middleware logic can be put just at the beginning of an action:

...
const actions = {
    close(context, payload) {
        //middleware code
        
        //your action
    }
}

@kiaking
Copy link
Member

kiaking commented Apr 21, 2020

Currently, Vuex has subscribeAction with before option where you can do something before an actions gets executed. I think this PR could be closed due to its over age.

I'll keep this issue open for now to gather feedbacks if there's any.

@kiaking
Copy link
Member

kiaking commented Apr 28, 2020

Closing due to inactivity. I think this can be done by subscribeAction 👍

@kiaking kiaking closed this Apr 28, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants