Skip to content
This repository has been archived by the owner on Mar 23, 2023. It is now read-only.

Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. #133

Closed
volkanunsal opened this issue Jan 20, 2015 · 9 comments
Closed

Comments

@volkanunsal
Copy link

I have an action that performs a long-running operation. When it's done, I want to tell it to signal it's done by dispatching another action. But when I do that, I come across the error above. My setup looks like this:

doSomeLongRunningStuff = ->
  # Does something 
  if isDone()
    MapActions.initDone()
MapStore =  _.extend {}, MicroEvent::,

  dispatchIndex: Dispatcher.register (payload) ->
    action  = payload.action
    type    = action.actionType
    announce = true

    switch type
      when C.INIT 
        doSomeLongRunningStuff()
      when C.INIT_DONE then ''
      else 
        # Don't trigger change because the action is not in this store
        announce = false

    if announce 
      console.log type 
      MapStore.trigger(CHANGE_EVENT)

I also tried throwing the initDone() callback to the next event loop by delaying it for 1 ms. That prevented the error, but then the change event was not being picked up by the views.

@volkanunsal
Copy link
Author

My bad. I had the listener on the views misconfigured. The setTimeout trick actually works.

@fisherwebdev
Copy link
Contributor

Do you need to block the code execution while the long-running stuff is happening? If not, you might consider putting it in a promise, and then issuing the action when it's complete. This would be a bit more like the way XHR calls work -- the asynchronous nature of what you're doing is more upfront, rather than a hack at the end.

The setTimeout trick is really a hack, and I think it's a somewhat dangerous habit to get into. Most of the time, these dispatch-within-a-dispatch errors are telling us that we have something wrong with our design, that we are thinking sequentially. The usual fix is to back up and build the second action into the first action. But with truly long-running stuff this isn't possible, and if an asynchronous solution is possible, it tends to be the best route.

@volkanunsal
Copy link
Author

Yeah, it's a hack. I tried a few other things, like using a different store and putting a waitFor on the original store, but nothing helped except for this one thing. Using a promise might be more explicit, though, and I'll give that a try. Thanks, @fisherwebdev!

@adeperio
Copy link

So I'm having this same problem. I have a situation where I do need to chain actions.

  1. Toggle an active flag,
  2. If flag is active perform a request.

I run into the "middle of a dispatch" scenario with this, as when the callback of the first action is fired, I would like to fire the second action based on the outcome of the first action (which I fire from the react component).

I can't see a way to "back up and build the second action into the first action" as the firing of the second action completely depends on the result (ie the result of a calculation in the store) of the first one.

I guess creating another store and using waitFor sounds like a solution, but to me that route seems to be deferring the problem, and results in the action being chained anyway, just across stores

I've heard creating a queue is also another solution (#106), but again to me the net affect is that you have chained the actions anyway, and has also been suggested that that solution is not optimal as it requires trusting developers not to use AppDispatcher.assign() in the stores (I'm also not sure why we trust them not to fire actions in the stores, but seems that we can't trust them to not use AppDispatcher.assign() in stores)

And setTimeout seems to work, but as suggested above, this may be a code smell as well?

It seems the above are 3 ways to enable the chaining of actions in Flux, all are noted as "hacks" or sub optimal solutions, but I'm struggling to find an example of a design which avoids having to do the above, but still allowing me to do what I need to do.

@volkanunsal
Copy link
Author

I can't see a way to "back up and build the second action into the first action" as the firing of the second action completely depends on the result (ie the result of a calculation in the store) of the first one.

@adeperio I actually managed to get around this issue by adding more logic into my actions. You can also achieve the same effect by having the store just do everything, but to keep the store free of control flow logic, you can put this kind of decision into the actions. I also had to modify the original action trigger to send a bit more data to the action.

@adeperio
Copy link

I was thinking about this, and wanted to go down this route as well. The only thing was that actions would then need to observe state changes in the stores right which meant observing store states as the second action waited for the first?

@volkanunsal
Copy link
Author

Not necessarily. If your actions depend on the state of the store, they aren't actions. The "view action" by definition is a UI event performed by a user. The "server action" is an event performed by the server, e.g. callback of an API call. All other events that stores perform, e.g. starting to fetch data, are internal events and shouldn't be called via actions.

@adeperio
Copy link

OK, so does that mean I should fire a http request from the Store? Using superagent my request would look something like the code below. If I run this code in the store wouldn't it mean that the server action would be triggered in the Store? If so does this break the Flux pattern?

http.get(url)
      .accept('application/json')
      .end((err, res) => {
        if(!err){
          Dispatcher.handleServerAction({
            actionType: ActionTypes.SOME_SERVER_RES,
            data: res.body
          });
        }
      });

@volkanunsal
Copy link
Author

That looks good to me. I use the same pattern but I placed it in an API adapter file that is solely responsible for data fetching, and it's called from a view action.

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

3 participants