4

I'm trying to understand Flux pattern.

I believe that in any good design the app should consist of relatively independent and universal (and thus reusable) components glued together by specific application logic.

In Flux there are domain-specific Stores encapsulating data and domain logic. These could be possibly reused in another application for the same domain.

I assume there should also be application-specific Store(s) holding app state and logic. This is the glue.

Now, I try to apply this to imaginary "GPS Tracker" app:

...

When a user clicks [Stop Tracking] button, corresponding ViewController raises STOP_CLICK.

  • AppState.on(STOP_CLICK):

    • dispatch(STOP_GEOLOCATION)
    • dispatch(STOP_TRACKING)
  • GeolocationService.on(STOP_GEOLOCATION):

    • stopGPS(); this.on = false; emit('change')
  • TrackStore.on(STOP_TRACKING):

    • saveTrack(); calcStatistics(); this.tracking = false; emit('change')
    • dispatch(START_UPLOAD)

So, I've got an event snowball.

It is said that in Flux one Action should not raise another. But I do not understand how this could be done.

I think user actions can't go directly to domain Stores as these should be UI-agnostic. Rather, AppState (or wherever the app logic lives) should translate user actions into domain actions.

  1. How to redesign this the Flux way?
  2. Where should application logic go?
  3. Is that correct to try to keep domain Stores independent of the app logic?
  4. Where is the place for "services"?

Thank you.

jww
  • 97,681
  • 90
  • 411
  • 885
sas18
  • 161
  • 11

1 Answers1

4

All of the application logic should live in the stores. They decide how they should respond to a particular action, if at all.

Stores have no setters. The only way into the stores is via a dispatched action, through the callback the store registered with the dispatcher.

Actions are not setters. Try not to think of them as such. Actions should simply report on something that happened in the real world: the user interacted with the UI in a certain way, the server responded in a certain way, etc.

This looks a lot like setter-thinking to me:

dispatch(STOP_GEOLOCATION)

dispatch(STOP_TRACKING)

Instead, dispatch the thing that actually happened: STOP_TRACKING_BUTTON_CLICKED (or TRACKING_STOPPED, if you want to be UI-agnostic). And then let the stores figure out what to do about it. All the stores will receive that action, and they can all respond to it, if needed. The code you have responding to two different actions should be responding to the same action.

Often, when we find that we want dispatch within a dispatch, we simply need to back up to the original thing that happened and make the entire application respond to that.

Community
  • 1
  • 1
fisherwebdev
  • 12,658
  • 4
  • 28
  • 27
  • It looks like my desire to "have AppStore orchestrate domain-specific Stores" is incompatible with Flux. Is your answer to make domain-specific Stores react on UI actions directly (and thus make them app-specific too) and not pursue mythical independence and reusability? Thanks – sas18 Mar 31 '15 at 08:48
  • There's nothing app-specific about TRACKING_STOPPED. There is something tracking-specific about it, however. So if you want a GeolocationStore to be able to respond to that, you do need to write the code to do that. Luckily, the dispatcher is already sending all the actions to all the stores, so you're halfway there. Now let's say, after you write that code, you want to move GeolocationStore to an app that doesn't use tracking. Will you have some extra stuff in GeolocationStore that you aren't using? Yes. But this does not make it dependent on UI or otherwise non-reusable. – fisherwebdev Apr 01 '15 at 05:22
  • Still not clear. So, when the user clicks the button some ViewController fires TRACKING_STOPPED and Stores update themselves. Now, I need to display a confirmation dialog to the user. So, I change the action fired to TRACKING_STOP_REQUEST and AppStore changes so that confirmation is displayed which will or will not rise TRACKING_STOPPED. So far so good. Now, I add to AppStore some logic determining if we need that confirmation or not: `if (needConfirmation) this.state.showStopConfirmation = true else fire(TRACKING_STOPPED)` So, I've got an action chain. How to handle this properly? Thank you. – sas18 Apr 10 '15 at 14:04
  • Stores don't fire actions. Actions may only result from some change in the outside world: the user clicked, the server responded, the browser started a new animation frame, etc. So the problem here is that you're thinking about actions as being setters. Think of them as newspapers, reporting the news about the outside world. Note how all of your examples use an imperative name for the actions. My suggestions here are all in the past tense: they've already happened; the action is merely a report. – fisherwebdev Apr 12 '15 at 05:50
  • So if you need to stop tracking in response to TRACKING_STOP_REQUESTED in StoreB only if StoreA says you should, then you would respond to the action in both stores. In StoreB you would `waitFor(StoreA.getDispatchToken())` and then call `StoreA.getFoo()` to determine if you should stop tracking. – fisherwebdev Apr 12 '15 at 05:55
  • So, to implement this "maybe confirm stop" behaviour in addition to adding some logic to AppStore I need to change ALL other stores that were listening to TRACKING_STOPPED and make them to: 1. Intercept TRACKING_STOP_REQUESTED 2. waitFor AppStore to decide if confirmation is required 3. Check, if AppStore decided to stop instantly and act or not 4. Intercept TRACKING_STOP_CONFIRMED Did I understand you correctly? If so, there is something very wrong in need to change multiple components. Thank you. – sas18 Apr 13 '15 at 10:34
  • Should I possibly implement this "do we need a confirmation?" logic in StopButtonPressed ActionCreator which will fire TRACKING_STOP_REQUESTED or TRACKING_STOPPED? Is that OK to have some logic in ActionCreators? Should I then move them into appropriate Store? – sas18 Apr 13 '15 at 11:08
  • I'm I right dividing Stores into 1. "Entity Stores" representing (part of) world state and domain logic and 2. "Presentation Stores" holding application state and logic? – sas18 Apr 13 '15 at 11:12
  • There is another case very similar to the above but more common and generic: 1. User submits a form. 2. ViewController calls `EntityStore.createEntity(data)` 3. `createEntity` validated the data. 4. If validation failed fires `ENTITY_CREATION_FAILED` 5. If validation passed (optimistically) fires `ENTITY_CREATED` 6. Sends it to the server. 7. Upon receiving server response fires either `ENTITY_CREATION_SUCCEED` or `ENTITY_CREATION_FAILED` Is that a Flux way? Thank you. – sas18 Apr 13 '15 at 11:22
  • Note: `EntityStore.createEntity` above does no mutations. It is just an ActionCreator - it fires Actions. `EntityStore` than catches `ENTITY_CREATED` and actually creates it. Other Stores interested in `ENTITY_CREATED` could have to waitFor `EntityStore`. – sas18 Apr 13 '15 at 11:33
  • Why doesn't the button itself have access to whatever state would determine which action to fire? Not that different from a toggle. That state could get passed down to it through props. A very simple if-else in the view is not a smell, to me. Now, if it needs to be more dynamic than that, you have something complex going on that needs to be handled with waitFor, as I described. – fisherwebdev Apr 13 '15 at 15:36