React Flux - dispatching within a dispatch - how to avoid?
Asked Answered
D

2

9

I seem to have encountered a situation where I cannot avoid the dispatch-within-a-dispatch problem in Flux.

I've read a few similar questions regarding this problem but none of them seem to have a good solution besides setTimeout hacks, which I would like to avoid.

I'm actually using alt.js instead of Flux but I think the concepts are the same.

Scenario

Imagine a component that initially renders a login form. When a user logs in, this triggers an XHR that eventually responds with authentication information (eg. the user name), and then fetches some secure data based on the authentication information and renders it instead of the login form.

The problem I have is when I attempt to fire an action to fetch data based on the XHR response, it is still in the dispatch of the LOGIN_RESPONSE action, and triggers the dreaded

Error: Invariant Violation: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.

Example

I have created this jsfiddle to demonstrate the problem.

I have a Wrapper component which either renders a login button or a Contents child component, based on whether the user is set in MyStore.

  1. First, the login button is rendered in Wrapper component.
  2. Clicking the button dispatches the LOGIN action.
  3. After a delay, the LOGIN_RESPONSE action is dispatched (via the async mechanism in alt.js).
  4. This action triggers MyStore to update the user name.
  5. Wrapper component observes the store change and updates its state.
  6. This causes Wrapper to render Content component instead of the login button.
  7. Content component, on mount, attempts to dispatch the FETCH_DATA action, which fails because the dispatcher is still dispatching LOGIN_RESPONSE. (If I wrap the FETCH_DATA dispatch in a setTimeout it works, but it feels like a hack).

Variations of this seems to be a common scenario. In fact almost all the related questions have a similar scenario, but with no good or concrete answers.

Is there something intrinsically wrong with this data flow? What is the proper Flux way of doing something like this?

Danit answered 16/1, 2016 at 15:48 Comment(0)
U
2

This is a common problem with dispatching in componentDidMount in many libraries. The solution is to wrap dispatches in React's batched updates; luckily, Alt allows you to do this with the batchingFunction option:

var alt = new Alt({
  // React.addons.batchedUpdates is deprecated:
  // batchingFunction: React.addons.batchedUpdates

  // use this instead in newer versions of React
  // see https://discuss.reactjs.org/t/any-plan-for-reactdom-unstable-batchedupdates/1978
  batchingFunction: ReactDOM.unstable_batchedUpdates
});

See https://jsfiddle.net/BinaryMuse/qftyfjgy/ for a working example and this Fluxxor issue for a description of the same problem in a different framework.

Unglue answered 17/1, 2016 at 9:10 Comment(0)
A
0

I believe our loyal friend, the Dispatcher, has its right to complain.

I will try to describe a hypothetical situation before throwing my conclusions. Let's say an app has two stores S1 and S2 and two kinds of actions A1 and A2. The right flow of an usual Flux implementation should be something like:

  1. Component fires an action A1 (basically a dispatch);
  2. Single dispatcher distribute correspondent payload to all registered stores;
  3. S1 consumes the payload and maybe updates its state;
  4. All components listening to changes in S1 check for changes they are interested in and maybe update their internal states (possibly triggering an re-render);
  5. S2 consumes ... (like in step 3)
  6. All components listening to changes in S2... (like in step 4)
  7. Now all stores are done dealing with the action payload, components can fire new actions (A1 or A2).

One of the greatest advantages of using Flux over traditional MVC is that Flux gives you The Gift of Predictability. This feeling empowers the developer in such a way that they believe that, by correctly applying the Flux philosophy, they are sure that the order of execution is always somewhat similar to:

A1 > S1 > S2 > A2 > S1 > S2 > ...

This is a really great deal, especially when trying to find sources of bugs. One could argue that enforcing a predictable chain of events can lead to some inefficiency and he is probably right, especially when dealing with async calls, but that is the price you pay for having such a great power!

Due to the async calls, things can get a little messy. Something like the following chain of events could happen:

A1 > S1 > A2 > S2 > S1 > S2 > ...

Maybe your app can handle such chain of events quite well, but such "unpredictability" hurts basic motivations behind Flux's unidirectional data flow.

I feel there is no consensus in the community on how to handle such situations, but I will share my general solution: "for the sake of predictability, make sure to not trigger any new actions before you have the last one totally processed".

One way of doing this is by downloading all the necessary (new) data the app needs to re-render successfully before triggering any additional actions. In your example, this could be achieved by first downloading the data involved in the LOGIN_RESPONSE and FETCH_DATA actions and wrapping it in a single payload and then dispatch it, so all the components will have the data they want already in the stores without asking for more.

Asquint answered 26/1, 2016 at 3:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.