Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AJAX in Flux: Refreshing stores when dependent state changes

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.

like image 591
optilude Avatar asked Nov 09 '22 19:11

optilude


1 Answers

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;

    ...
  }
});
like image 175
Adam Stone Avatar answered Nov 15 '22 11:11

Adam Stone