I'm building a Flux app using MartyJS (which is pretty close to "vanilla" Flux and uses the same underlying dispatcher). It contains stores with an inherent dependency relationship. For example, a UserStore
tracks the current user, and an InstanceStore
tracks instances of data owned by the current user. Instance data is fetched from an API asynchronously.
The question is what to do to the state of the InstanceStore
when the user changes.
I've come to believe (e.g. reading answers by @fisherwebdev on SO) that it's most appropriate to make AJAX requests in the action creator function, and to have an AJAX "success" result in an action that in turn causes stores to change.
So, to fetch the user (i.e. log in), I'm making an AJAX call in the action creator function, and when it resolves, I'm dispatching a RECEIVE_USER
action with the user as a payload. The UserStore
listens to this and updates its state accordingly.
However, I also need to re-fetch all the data in the InstanceStore
if the user is changed.
Option 1: I can listen to RECEIVE_USER
in the InstanceStore
, and if it is a new user, trigger an AJAX request, which in turn creates another action, which in turn causes the InstanceStore
to update. The problem with this is that it feels like cascading actions, although technically it's async so the dispatcher will probably allow it.
Option 2: Another way would be for InstanceStore
to listen to change events emitted by UserStore
and do the request-action dance then, but this feels wrong too.
Option 3: A third way would be for the action creator to orchestrate the two AJAX calls and dispatch the two actions separately. However, now the action creator has to know a lot about how the stores relate to one another.
One of the answers in Where should ajax request be made in Flux app? makes me think option 1 is the right one, but the Flux docs also imply that stores triggering actions is not good.
Something like option 3 seems like the cleanest solution to me, followed by option 1. My reasoning:
Option 2 deviates from the expected way of handling dependencies between stores (waitfor), and you'd have to check after each change event to figure out which ones are relevant and which ones can be ignored, or start using multiple event types; it could get pretty messy.
I think option 1 is viable; as Bill Fisher remarked in the post you linked, it's OK for API calls to be made from within stores provided that the resulting data is handled by calling new Actions. But OK doesn't necessarily mean ideal, and you'd probably achieve better separation of concerns and reduce cascading if you can collect all your API calls and action initiation in one place (i.e. ActionCreators). And that would be consistent with option 3.
However, now the action creator has to know a lot about how the stores relate to one another.
As I see it, the action creator doesn't need to know anything about what the stores are doing. It just needs to log in a user and then get the data associated with the user. Whether this is done through one API call or two, these are logically very closely coupled and make sense within the scope of one action creator. Once the user is logged in and the data is obtained, you could fire two actions (e.g. LOGGED_IN, GOT_USER_DATA) or even just one action that contains all the data needed for both. Either way, the actions are just echoing what the API calls did, and it's up to the stores to decide what to do with it.
I'd suggest using a single action to update both stores, because this seems like a perfect use case for waitfor
: when one action triggers a handler in both stores, you can instruct InstanceStore to wait for UserStore's handler to finish before InstanceStore's handler executes. It would look something like this:
UserStore.dispatchToken = AppDispatcher.register(function(payload) {
switch (payload.actionType) {
case Constants.GOT_USER_DATA:
...(handle UserStore response)...
break;
...
}
});
...
InstanceStore.dispatchToken = AppDispatcher.register(function(payload) {
switch (payload.actionType) {
case Constants.GOT_USER_DATA:
AppDispatcher.waitFor([UserStore.dispatchToken]);
...(handle InstanceStore response)...
break;
...
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With