At what nesting level should components read from Stores in Flux?

I am rewriting my application to use Flux, and I have a problem retrieving data from Stores. I have many components and they nest a lot. Some of them are large ( Article ), some are small and simple ( UserAvatar , UserLink ).

I struggled with where in the component hierarchy I should read data from stores.
I tried two extreme approaches, none of which I liked:

All entity components read their own data.

Each component that needs some data from the Store receives only the object identifier and receives the object itself.
For example, Article is passed to articleId , UserAvatar and UserLink are passed to userId .

This approach has several significant drawbacks (discussed in the sample code).

 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> ) } }); 

The disadvantages of this approach are:

  • This frustrates the availability of 100s components that could potentially be subscribed to stores;
  • It is difficult to track how the data is updated and in what order , because each component independently extracts its data;
  • Even if you already have an entity in a state, you are forced to pass on your identifier to children who will receive it again (or violate consistency).

All data is read once at the top level and passed to the components

When I was tired of tracking errors, I tried to put all the data at the top level. However, this turned out to be impossible, because for some objects I have several levels of nesting.

For example:

  • A Category contains UserAvatar people who contribute to this category;
  • Article may have several Category s.

Therefore, if I wanted to get all the data from Stores at the Article level, I would need:

  • Get an article from ArticleStore ;
  • Retrieve all article categories from CategoryStore ;
  • Separately, extract contributors from each category from the UserStore ;
  • Somehow pass all this data to the components.

Even more disappointing, when I need a deeply nested entity, I will need to add code for each level of nesting to further convey it.

Summarizing

Both approaches seem erroneous. How can I solve this problem most elegantly?

My goals:

  • Stores should not have an insane number of subscribers. It is stupid for every UserLink to listen to the UserStore if the parent components already do this.

  • If the parent component retrieved some object from the repository (for example, user ), I do not want the nested components to come to it again. I would have to pass it through the props.

  • I will not need to get all entities (including relationships) at the top level, because this will complicate the addition or removal of relationships. I do not want to introduce new details at all levels of nesting every time a nested object receives new relationships (for example, a category receives a curator ).

+82
javascript reactjs reactjs-flux
Sep 06 '14 at 14:16
source share
4 answers

The approach I came to is that each component receives its data (and not identifiers) as a support. If any nested component needs a related object, it returns it to the parent component.

In our example, Article should have an Article prop, which is an object (supposedly retrieved using an ArticleList or ArticlePage ).

Since Article also wants to display UserLink and UserAvatar for the author of the article, he will subscribe to UserStore and keep author: UserStore.get(article.authorId) in his state. It will then display UserLink and UserAvatar using this this.state.author . If they want to pass it on, they can. No child components will need to download this user again.

Repeat:

  • No component ever receives an identifier as a support; all components get their respective objects.
  • If the child components need the object, it is responsible for receiving and transferring it as a support.

This solves my problem well. Sample code has been 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 the most stupid components stupid, but does not make us complicate the hell out of top-level components.

+33
Sep 06 '14 at 14:16
source share

Most people start by listening to the respective stores in the controller component located at the top of the hierarchy.

Later, when it seems that a lot of irrelevant details are being passed through the hierarchy to some deeply nested component, some people decided that it was a good idea to let the deeper component listen to changes in the stores. This provides better encapsulation of the problem area to which this deeper branch of the component tree belongs. There are good arguments for doing this wisely.

However, I prefer to always listen upstairs and just transfer all the data. Sometimes I even take the entire state of the store and pass it hierarchically as a single object, and I will do this for several stores. So I would have support for the ArticleStore state, and another for the UserStore state, etc. I believe that avoiding deeply nested representations of the controller maintains a unique entry point for the data and unifies the data flow. Otherwise, I have several data sources, and it can become difficult to debug.

Type checking is harder with this strategy, but you can customize the "form" or type template for a large object using React PropTypes. See: https://github.com/facebook/react/blob/master/src/core/ReactPropTypes.js#L76-L91 http://facebook.imtqy.com/react/docs/reusable-components.html#prop -validation

Please note that you can put the logic of data binding between stores in the stores themselves. Thus, your ArticleStore can waitFor() UserStore and include the corresponding users with each Article record that it provides through getArticles() . Doing this in your views sounds like pushing logic into the presentation layer, which is a practice that you should avoid whenever possible.

You may also be tempted to use transferPropsTo() , and many people like to do this, but I prefer to keep everything explicit for readability and therefore maintainability.

FWIW, I understand that David Nolen takes a similar approach with his Om card (which is somewhat Flux-compatible ) with a single data entry point on the root node - the equivalent in Flux was to have only one controller scan that would listen to all stores. This is achieved using shouldComponentUpdate() and immutable data structures that can be compared by reference, with ===. For immutable data structures, you can view David mori or Facebook immutable-js . My limited knowledge of Om mainly comes from the Future JavaScript MVC Framework

+35
Sep 07 '14 at 8:45
source share

My solution is much simpler. Each component that has its own state is allowed to talk and listen to stores. These are very similar to the controller components. Deeper nested components that do not support state but simply display material are not allowed. They only get the props for pure rendering, very similar to the look.

Thus, everything happens from state components to stateless components. State data storage is considered low.

In your case, the article will be restrained and, therefore, negotiations with stores, UserLink, etc. will only display so that it receives article.user as prop.

0
Sep 06 '14 at 16:22
source share

The problems described in your two philosophies are common to any one-page application.

They are briefly discussed in this video: https://www.youtube.com/watch?v=IrgHurBjQbg and Relay ( https://facebook.imtqy.com/relay ) was developed by Facebook to overcome the compromise you are describing.

The relay approach is very data oriented. This is the answer to the question "How to get only the necessary data for each component in this view in one request to the server?" And at the same time, Relay ensures that you have few code connections when a component is used in multiple views.

If Relay is not an option, "all components of the entity read their own data", it seems to me that this is the best approach to me for the described situation. I think the confusion in Flux is what the store has. A store concept is not a place to store a model or collection of objects. Stores are temporary places where your application places data before rendering the presentation. The real reason they exist is to solve the problem of dependencies between the data that goes to different stores.

That Flux does not indicate how storage is related to the concept of models and the collection of objects (a la Backbone). In this sense, some people actually create a flux repository where you can place a collection of objects of a certain type that are not dumped for the whole time the user keeps the browser open, but since I understand the flow, this is not the right store.

The solution is to have a different level at which you save and update the objects needed to render your view (and potentially more). If you use this layer for abstract models and collections, this is not a problem if you subcomponents must request their own data again.

0
Oct. 16 '15 at 22:03
source share



All Articles