React Flux implement dispatch chain
Asked Answered
G

3

11

i'm trying to use React with Flux architecture and stumbled on one restriction which i can't handle. Problem is as following:

  1. There's a store which listens to an event. Event has object id. We need to fetch object if needed and make it selected.
  2. If store doesn't have object with this id - it's queried. In callback we dispatch another event to store which is responsible for selection.
  3. If store has object - i'd like to dispatch selection event, but i can't because dispatch is in progress.

Best solution i came up with so far is wrapping inner dispatch in setTimeout(f, 0), but it looks scary.

Actually the problem is quite general - how should i organize dispatch chain without dispatch nesting (without violating current Flux restrictions) if each new dispatch is based on previous dispatch handling result.

Does anybody have any good approaches to solve such problems?

var selectItem(item) {
    AppDispatcher.dispatch({
        actionType: AppConstants.ITEM_SELECT,
        item: item
    });
}

// Item must be requested and selected.
// If it's in store - select it.
// Otherwise fetch and then select it.
SomeStore.dispatchToken = AppDispatcher.register((action) => {
    switch(action.actionType) {
        case AppConstants.ITEM_REQUESTED:
            var item = SomeStore.getItem(action.itemId);
            if (item) {
                // Won't work because can't dispatch in the middle of dispatch
                selectItem(item);
            } else {
                // Will work
                $.getJSON(`some/${action.itemId}`, (item) => selectItem(item));
            }
    }
};
Goddard answered 21/4, 2015 at 20:3 Comment(0)
L
2

Are you writing your own dispatcher? setTimeout(f, 0) is a fine trick. I do the same thing in my minimal flux here. Nothing scary there. Javascript's concurrency model is pretty simple.

More robust flux dispatcher implementations should handle that for you.

Laughter answered 21/4, 2015 at 20:25 Comment(7)
Hooray for micro flux implementations :) Not like there aren't a ton already, but here's mine captray.github.io/vanilla-fluxStacey
And regarding the question, it's ok to create new Actions from within a store, but it sounds like you're attempting to do so before your Dispatcher has finished dispatching. However, seeing the word "selected/selection" in there sounds like UI state is creeping into your App State. It might be worth rethinking or retooling your Actions. Tough to say without more context.Stacey
@z5h No, i was trying to stick to fb implementation. Tried to search solutions on the web but found only people saying dispatch chain is really bad. I don't like trick with setTimeout(f, 0) because it looks like i'm doing the same chain (which i don't actually think is bad but tend to trust smart guys) with workarounds. Thanks for the MDN link! It seems i really need some fundamental knowledge!Goddard
@Stacey Your implementation is very similar to fb's one (in terms of functionality). On question: exactly, i'm trying to dispatch from another dispatch, because only second dispatch place has data to dispatch on. I'm storing selection in App State because there's a concept of current selection for the entire application. Maybe it's really not that good idea.Goddard
It's actually exactly the same. I just included a custom JS event and a Reactor instead of depending on Event Emitter or other eventing solutions. Again, it's difficult to say without seeing the code. Point 1 above says that your store is listening to an event? Stores should really only received data payloads via the callback function provided to the Dispatcher when the Store registers with the Dispatcher. Is your Store listening to some other event outside of the regular Dispatcher -> Store -> Component flow?Stacey
@Stacey I added the example. The place i'm interested in doing is the first call to selectItem. I made it work by wrapping with setTimeout(f, 0). Actually i followed your advice and moved selection info into near-root component state, but i'm pretty sure i'll encounter similar problems soon.Goddard
It's a shame there is no solid implementation that ditches setTimeout nasty trick yetTutor
P
2

If ITEM_SELECT is an event that another Store is going to handle:

You are looking for dispatcher.waitFor(array<string> ids): void, which lets you use the SomeStore.dispatchToken that register() returns to enforce the order in which Stores handle an event.

The store, say we call it OtherStore, that would handle the ITEM_SELECT event, should instead handle ITEM_REQUEST event, but call dispatcher.waitFor( [ SomeStore.dispatchToken ] ) first, and then get whatever result is interesting from SomeStore via a public method, like SomeStore.getItem().

But from your example, it seems like SomeStore doesn't do anything to its internal state with ITEM_REQUEST, so you just need to move the following lines into OtherStore with a few minor changes:

// OtherStore.js
case AppConstants.ITEM_REQUESTED:
        dispatcher.waitFor( [ SomeStore.dispatchToken ] );// and don't even do this if SomeStore isn't doing anything with ITEM_REQUEST
        var item = SomeStore.getItem(action.itemId);
        if (item) {
            // Don't dispatch an event, let other stores handle this event, if necessary
            OtherStore.doSomethingWith(item);
        } else {
            // Will work
            $.getJSON(`some/${action.itemId}`, (item) => OtherStore.doSomethingWith(item));
        }

And again, if another store needs to handle the result of OtherStore.doSomethingWith(item), they can also handle ITEM_REQUESTED, but call dispatcher.waitFor( [ OtherStore.dispatchToken ] ) before proceeding.

Pyonephritis answered 4/6, 2015 at 18:3 Comment(2)
examples doesn't show it but SomeStore actually handles message. Your approach is nice and actually solve a problem i guess but it doesn't solve problem in general. If SomeStore during message handling can dispatch different action types, then with your approach you have to carry this logic all over endpoint stores.Goddard
What I'm trying to say is SomeStore shouldn't dispatch different action types during message handling. The Flux developers think this as well, which is why they included waitFor among the few methods available in their Dispatcher implementation. waitFor does solve the problem in general. I think you should consider event chaining as an anti-pattern and reconsider how you handle events. Perhaps include more details of your specific example?Pyonephritis
S
0

So, in looking at your code, are you setting a "selected" property on the item so it will be checked/selected in your UI/Component? If so, just make that part of the function you are already in.

if(item) {
    item.selected = true;
    //we're done now, no need to create another Action at this point, 
    //we have changed the state of our data, now alert the components 
    //via emitChange()

    emitChange();
}

If you're wanting to track the currently selected item in the Store, just have an ID or and object as a private var up there, and set it similarly.

var Store = (function(){

    var _currentItem = {};
    var _currentItemID = 1;

    function selectItem(item) {

          _currentItem = item;
          _currentItemID = item.id;
          emitChange();
    }


    (function() {
         Dispatcher.register(function(action){
               case AppConstants.ITEM_REQUESTED:
                   var item = SomeStore.getItem(action.itemId);
                   if (item) {
                        selectItem(item);
                   } else {
                   $.getJSON(`some/${action.itemId}`, (item) => 
                        selectItem(item);   
                   }
         });
    })();


    return {
         getCurrentlySelectedItem: function() {
              return _currentItem;
         },
         getCurrentlySelectedItemID: function() {
              return _currentItemID;
         }
    }

})();

Ultimately, you don't have to create Actions for everything. Whatever the item is that you're operating on, it should be some domain entity, and it is your Store's job to manage the state of that specific entity. Having other internal functions is often a necessity, so just make selectItem(item) an internal function of your Store so you don't have to create a new Action to access it or use it.

Now, if you have cross-store concerns, and another Store cares about some specific change to some data in your initial Store, this is where the waitFor(ids) function will come in. It effectively blocks execution until the first Store is updated, then the other can continue executing, assured that the other Store's data is in a valid state.

I hope this makes sense and solves your problem, if not, let me know, and hopefully I can zero in better.

Stacey answered 23/4, 2015 at 14:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.