So, let's say you have a basic Flux action that fetches remote data asynchronously, and then dispatches that data to its store. The dispatch is called after the async fetch completes, using a promise or callback. Nothing out of the ordinary here! Just your standard async control flow patterns.
Next, let's say we take this action, tie it to a button's onClick, and then we click it. If the async call takes a bit of time to complete (as they tend to do), that click would feel like it either didn't go through or threw an error, because any components listening to the store would do absolutely nothing until the callback completes or the promise resolves (which is when the dispatch will occur).
If they don't, interactions feel unresponsive, and the app will feel 'broken' in many situations. It will be frustrating to use at the very least!
Flux says:
- an action represents an intent to make a discrete change to a store, and
- stores should properly reflect your app's state at all times.
In my opinion, the fact that state change will occur is non-trivial information; it's important app state! That is to say, the 'knowledge of itention' itself is information that its important to convey to the user.
Given my philosophies, and what we know about Flux, I think:
- actions should always dispatch synchronously
- only actions that make async calls, 'async actions', are permitted to call other actions
- successfully calling an async action should change the store at least twice
- successfully calling a sync action should change the store one or zero times
- handle async call completion and failure. always handle both cases, if you can
- an async action's disptach tell a store change into a 'transition state' (by setting its values to their defaults, or an isLoading flag to true, etc)
- transition states should be canceleable (async call doesn't fire callback, async call errors, etc)
If you don't reset stores between async fetches, as you navigate around the app, old data will still be in the store while the fetch takes place, and components will be rendered with this old/expired state while the new fetch happens. This means that stores will send very wrong data to certain components, in a lot of cases.
For instance, let's say you:
- start at DocumentList
- edit Document1
- go back to DocumentList
- edit Document2
Unless the fetchDocument action resets its store while it's fetching, Document1 will still be in the store during initial component render.. so Document1 will be rendered on Document2's page for however long the fetch takes!! Fetch is REALLY quick on localhost, of course, so you won't notice this as you dev.. but it becomes a real problem in production, when requests take real time and do real work.
With login/logout systems, this can actually cause security holes, UI navigation errors, and other strange deeply-rooted bugs. And you likely won't notice these bugs as you dev, because they only become apparent as you navigate around the app naturally and make async calls that take more than a few milliseconds... while developing, we tend to be constantly refreshing/resetting browser/session state, and making requests to localhost that complete almost instantly, but that's not how users will experience your app in production at all.
Alt.js, a Flux implementation, addresses this, somewhat! Its solution (in its getting-started
tutorial) is to combine two actions: it has you call a set action
from within a fetch action
.
To boil it down, Alt.js asserts that actions with async calls should work like this:
component
callsfetch action
fetch action
dispatches tofetch store listener
1.fetch store listener
sets its store values to their defaultsfetch action
makes async call, passing a callback that will callset action
fetch action
returns, giving control back to thecomponent
that called it- any components that are listening to
store
get rendered (with the default store values)
Some time later:
- the event loop comes across
async call
and executes it async call
does work or gets data, and then fires offcallback
2.callback
callsset action
set action
dispatches the results ofasync call
toset store listener
set store listener
sets its state according to the results ofasync call
, and returnsset action
returns, giving control back tocallback
2.callback
returns, giving control back toasync call
async call
returns, giving control back the event loop- the event loop moves along, working through its messaging queue
- the event loop comes across any
store listeners
and executes them - components that were listening for store changes update to match the new store state
- affected components get re-rendered (store state set according to the results of
async call
)