I'm rewriting my app to use Flux and I have an issue with retrieving data from Stores. I have a lot of components, and they nest a lot. Some of them are large (Article
), some are small and simple (UserAvatar
, UserLink
).
I've been struggling with where in component hierarchy I should read data from Stores.
I tried two extreme approaches, neither of which I quite liked:
Each component that needs some data from Store receives just entity ID and retrieves entity on its own.
For example, Article
is passed articleId
, UserAvatar
and UserLink
are passed userId
.
This approach has several significant downsides (discussed under code sample).
var Article = React.createClass({
mixins: [createStoreMixin(ArticleStore)],
propTypes: {
articleId: PropTypes.number.isRequired
},
getStateFromStores() {
return {
article: ArticleStore.get(this.props.articleId);
}
},
render() {
var article = this.state.article,
userId = article.userId;
return (
<div>
<UserLink userId={userId}>
<UserAvatar userId={userId} />
</UserLink>
<h1>{article.title}</h1>
<p>{article.text}</p>
<p>Read more by <UserLink userId={userId} />.</p>
</div>
)
}
});
var UserAvatar = React.createClass({
mixins: [createStoreMixin(UserStore)],
propTypes: {
userId: PropTypes.number.isRequired
},
getStateFromStores() {
return {
user: UserStore.get(this.props.userId);
}
},
render() {
var user = this.state.user;
return (
<img src={user.thumbnailUrl} />
)
}
});
var UserLink = React.createClass({
mixins: [createStoreMixin(UserStore)],
propTypes: {
userId: PropTypes.number.isRequired
},
getStateFromStores() {
return {
user: UserStore.get(this.props.userId);
}
},
render() {
var user = this.state.user;
return (
<Link to='user' params={{ userId: this.props.userId }}>
{this.props.children || user.name}
</Link>
)
}
});
Downsides of this approach:
When I was tired of tracking down bugs, I tried to put all data retrieving at the top level. This, however, proved impossible because for some entities I have several levels of nesting.
For example:
Category
contains UserAvatar
s of people who contribute to that category;Article
may have several Category
s.Therefore if I wanted to retrieve all data from Stores at the level of an Article
, I would need to:
ArticleStore
;CategoryStore
;UserStore
;Even more frustratingly, whenever I need a deeply nested entity, I would need to add code to each level of nesting to additionally pass it down.
Both approaches seem flawed. How do I solve this problem most elegantly?
My objectives:
Stores shouldn't have an insane number of subscribers. It's stupid for each UserLink
to listen to UserStore
if parent components already do that.
If parent component has retrieved some object from store (e.g. user
), I don't want any nested components to have to fetch it again. I should be able to pass it via props.
I shouldn't have to fetch all entities (including relationships) at the top level because it would complicate adding or removing relationships. I don't want to introduce new props at all nesting levels each time a nested entity gets a new relationship (e.g. category gets a curator
).
The approach at which I arrived is having each components receive its data (not IDs) as a prop. If some nested component needs a related entity, it's up to the parent component to retrieve it.
In our example, Article
should have an article
prop which is an object (presumably retrieved by ArticleList
or ArticlePage
).
Because Article
also wants to render UserLink
and UserAvatar
for article's author, it will subscribe to UserStore
and keep author: UserStore.get(article.authorId)
in its state. It will then render UserLink
and UserAvatar
with this this.state.author
. If they wish to pass it down further, they can. No child components will need to retrieve this user again.
To reiterate:
This solves my problem quite nicely. Code example rewritten to use this approach:
var Article = React.createClass({
mixins: [createStoreMixin(UserStore)],
propTypes: {
article: PropTypes.object.isRequired
},
getStateFromStores() {
return {
author: UserStore.get(this.props.article.authorId);
}
},
render() {
var article = this.props.article,
author = this.state.author;
return (
<div>
<UserLink user={author}>
<UserAvatar user={author} />
</UserLink>
<h1>{article.title}</h1>
<p>{article.text}</p>
<p>Read more by <UserLink user={author} />.</p>
</div>
)
}
});
var UserAvatar = React.createClass({
propTypes: {
user: PropTypes.object.isRequired
},
render() {
var user = this.props.user;
return (
<img src={user.thumbnailUrl} />
)
}
});
var UserLink = React.createClass({
propTypes: {
user: PropTypes.object.isRequired
},
render() {
var user = this.props.user;
return (
<Link to='user' params={{ userId: this.props.user.id }}>
{this.props.children || user.name}
</Link>
)
}
});
This keeps innermost components stupid but doesn't force us to complicate the hell out of top level components.