Imagine something like Quora.
[
{
type: "question",
answers: [
{
type: "answer",
upvotes: [
{
type: "upvote"
}
/* more upvotes */
],
comments [
{
type: "comment"
}
/* more comments */
]
}
/* more answers */
]
}
/* more questions */
]
I'd surely have something like a QuestionsStore
. But for all child entities I'm unsure what to do with them. Coming from Backbone I'm thinking every answer should have a UpvotesStore
and a CommentsStore
and components would get their data from these Stores and subscribe to updates from them. As far as I understand Flux, "child"/relational stores are somewhat uncommon.
When every component subscribes to updates from QuestionsStore
that leads to something like:
/* in CommentsComponent */
onUpdate: function() {
this.setState({
comments: QuestionsStore.getComments({questionId: 1, answerId: 1});
});
}
or more extreme:
/* in CommentComponent */
onUpdate: function() {
this.setState(QuestionsStore.getComment({questionId: 1, answerId: 1, commentId: 1}));
}
Since the relational data lives in a tree structure, every component needs to know all "parent" id's in order to be able to query their data from QuestionsStore
. I find this somehow weird.
So what is the best Flux pattern to deal with relational (one-to-many) data structure?
The two main options I see are to retain the nested structure, or to parse it into a flat structure where each object contains references to other objects according to their relationships. I think the best approach would depend on how the data will need to be accessed.
Keeping the nested structure makes things relatively easy if it always mirrors your view hierarchy. For example, consider:
// render a <Question/>
render: function() {
var question = this.props.data,
answers = question.answers.map(function(answer, i) {
return <Answer key={i} data={answer}/>
});
return (
<div className="question">
{answers}
</div>
);
}
// render an <Answer/>
render: function() {
var answer = this.props.data,
comments = answer.comments.map(function(comment, i) {
return <Comment key={i} data={comment}/>
});
return (
<div className="answer">
...
{comments}
</div>
);
}
// and so on
Having a top-level component get the data from the store and pass it through props like this is much easier to manage than having each component keep track of indices to find nested data in the Store.
The potential downside of this approach is that the nested data is harder to access from outside its context. For example, if you want to keep track of all Comments by a given User, your user data would still have to resort to indices to track which Questions, which Answer indices per Question, and which Comment indices per Answer point to the correct Comments. In cases like this, you might benefit from a flat data structure.
A flat structure takes more work to set up, but has the advantage that each object is immediately accessible. Assuming your data comes in as nested objects, it would need to be parsed by walking through the tree to find all the nodes, give each a unique key, replace its subtrees with references to the appropriate keys, and then store all the results in the appropriate stores. But as Dan Abramov pointed out, someone has already provided a module to facilitate this.
Now your Questions can refer to Answers and each Answer can refer to Comments, making it trivial to recreate the original nested structure; but your User can refer to Comments too, without having to keep track of how they're related to Answers or Questions.
This is basically how Git works internally; it's especially efficient for data trees that contain a lot of redundancy (e.g. multiple commits that include an unchanged file can all point to the same single object).
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