The Facebook Flux dispatcher explicitly prohibits ActionCreators from dispatching other ActionCreators. This restriciton is probably a good idea since it prevents your application from creating event chains.
This however becomes an issue as soon as you have Stores containing data from asynchronous ActionCreators that depend on each other. If CategoryProductsStore
depends on CategoryStore
there doesn't seem to be a way to avoid event chains when without resorting to deferring the follow-up action.
Scenario 1: A store containing a list of products in a category needs to know from which category ID it should fetch products from.
var CategoryProductActions = {
get: function(categoryId) {
Dispatcher.handleViewAction({
type: ActionTypes.LOAD_CATEGORY_PRODUCTS,
categoryId: categoryId
})
ProductAPIUtils
.getByCategoryId(categoryId)
.then(CategoryProductActions.getComplete)
},
getComplete: function(products) {
Dispatcher.handleServerAction({
type: ActionTypes.LOAD_CATEGORY_PRODUCTS_COMPLETE,
products: products
})
}
}
CategoryStore.dispatchToken = Dispatcher.register(function(payload) {
var action = payload.action
switch (action.type) {
case ActionTypes.LOAD_CATEGORIES_COMPLETE:
var category = action.categories[0]
// Attempt to asynchronously fetch products in the given category, this causes an invariant to be thrown.
CategoryProductActions.get(category.id)
...
Scenario 2:
Another scenario is when a child component is mounted as the result of a Store change and its componentWillMount
/componentWillReceiveProps
attempts to fetch data via an asynchronous ActionCreator:
var Categories = React.createClass({
componentWillMount() {
CategoryStore.addChangeListener(this.onStoreChange)
},
onStoreChange: function() {
this.setState({
category: CategoryStore.getCurrent()
})
},
render: function() {
var category = this.state.category
if (category) {
var products = <CategoryProducts categoryId={category.id} />
}
return (
<div>
{products}
</div>
)
}
})
var CategoryProducts = React.createClass({
componentWillMount: function() {
if (!CategoryProductStore.contains(this.props.categoryId)) {
// Attempt to asynchronously fetch products in the given category, this causes an invariant to be thrown.
CategoryProductActions.get(this.props.categoryId)
}
}
})
Are there ways to avoid this without resorting to defer?
Whenever you are retrieving the state of the application, you want to be retrieving that state directly from the Stores, with getter methods. Actions are objects that inform Stores. You could think of them as being like a request for a change in state. They should not return any data. They are not a mechanism by which you should be retrieving the application state, but rather merely changing it.
So in scenario 1, getCurrent(category.id)
is something that should be defined on a Store.
In scenario 2, it sounds like you are running into an issue with the initialization of the Store's data. I usually handle this by (ideally) getting the data into the stores before rendering the root component. I do this in a bootstrapping module. Alternatively, if this absolutely needs to be async, you can create everything to work with a blank slate, and then re-render after the Stores respond to an INITIAL_LOAD
action.
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