I'm creating a simple CRUD app using Facebook's Flux Dispatcher to handle the creation and editing of posts for an English learning site. I currently am dealing with an api that looks like this:
/posts/:post_id
/posts/:post_id/sentences
/sentences/:sentence_id/words
/sentences/:sentence_id/grammars
On the show and edit pages for the app, I'd like to be able to show all the information for a given post as well as all of it's sentences and the sentences' words and grammar details all on a single page.
The issue I'm hitting is figuring out how to initiate all the async calls required to gather all this data, and then composing the data I need from all the stores into a single object that I can set as the state in my top level component. A current (terrible) example of what I've been trying to do is this:
The top level PostsShowView:
class PostsShow extends React.Component {
componentWillMount() {
// this id is populated by react-router when the app hits the /posts/:id route
PostsActions.get({id: this.props.params.id});
PostsStore.addChangeListener(this._handlePostsStoreChange);
SentencesStore.addChangeListener(this._handleSentencesStoreChange);
GrammarsStore.addChangeListener(this._handleGrammarsStoreChange);
WordsStore.addChangeListener(this._handleWordsStoreChange);
}
componentWillUnmount() {
PostsStore.removeChangeListener(this._handlePostsStoreChange);
SentencesStore.removeChangeListener(this._handleSentencesStoreChange);
GrammarsStore.removeChangeListener(this._handleGrammarsStoreChange);
WordsStore.removeChangeListener(this._handleWordsStoreChange);
}
_handlePostsStoreChange() {
let posts = PostsStore.getState().posts;
let post = posts[this.props.params.id];
this.setState({post: post});
SentencesActions.fetch({postId: post.id});
}
_handleSentencesStoreChange() {
let sentences = SentencesStore.getState().sentences;
this.setState(function(state, sentences) {
state.post.sentences = sentences;
});
sentences.forEach((sentence) => {
GrammarsActions.fetch({sentenceId: sentence.id})
WordsActions.fetch({sentenceId: sentence.id})
})
}
_handleGrammarsStoreChange() {
let grammars = GrammarsStore.getState().grammars;
this.setState(function(state, grammars) {
state.post.grammars = grammars;
});
}
_handleWordsStoreChange() {
let words = WordsStore.getState().words;
this.setState(function(state, words) {
state.post.words = words;
});
}
}
And here is my PostsActions.js - the other entities (sentences, grammars, words) also have similar ActionCreators that work in a similar way:
let api = require('api');
class PostsActions {
get(params = {}) {
this._dispatcher.dispatch({
actionType: AdminAppConstants.FETCHING_POST
});
api.posts.fetch(params, (err, res) => {
let payload, post;
if (err) {
payload = {
actionType: AdminAppConstants.FETCH_POST_FAILURE
}
}
else {
post = res.body;
payload = {
actionType: AdminAppConstants.FETCH_POST_SUCCESS,
post: post
}
}
this._dispatcher.dispatch(payload)
});
}
}
The main issue is that the Flux dispatcher throws a "Cannot dispatch in the middle of a dispatch" invariant error when SentencesActions.fetch
is called in the _handlePostsStoreChange
callback because that SentencesActions method triggers a dispatch before the dispatch callback for the previous action is finished.
I'm aware that I can fix this by using something like _.defer
or setTimeout
- however that really feels like I'm just patching the issue here. Also, I considered doing all this fetching logic in the actions itself, but that seemed not correct either, and would make error handling more difficult. I have each of my entities separated out into their own stores and actions - shouldn't there be some way in the component level to compose what I need from each entity's respective stores?
Open to any advice from anyone who has accomplished something similar!
waitFor
? facebook.github.io/flux/docs/dispatcher.html – AlexandriawaitFor
, but it didn't really seem to address the issue, since the issue is that a second action gets dispatched before the first one can finish. However, maybe my understanding ofwaitFor
is wrong and I'm just not using it correctly? – Friulian