Hacker News new | comments | show | ask | jobs | submit login

From first-hand experience, I can say that React+Flux has scaled well to 8+ developers over 800+ JS files and ~60k lines of code in a large single page app here at Facebook. I'm happy to answer any questions! Some things that we've struggled with:

1. All data should be kept in stores. You may have some local component state, but it shouldn't be anything you want to persist if the component is unmounted. We have tried using state several times, and always go back to keeping it in singleton stores. It also works better with the action-dispatcher pattern.

2. Now all your data is in stores, but how do you get it into the specific component that needs it? We started with large top level components which pull all the data needed for their children, and pass it down through props. This leads to a lot of cruft and irrelevant code in the intermediate components. What we settled on, for the most part, is components declaring and fetching the data they need themselves, except for some small, more generic components. Since most of our data is fetched asynchronously and cached, we've created mixins that make it easy to declare which data your component needs, and hook the fetching and listening for updates into the lifecycle methods (componentWillMount, etc).

3. Actions don't have callbacks, as they are by design fire-and-forget. So if you need to be notified when some item has finished being created, for example, you need to listen for the follow up action that the CREATE action fires (yeah, actions firing actions, a bit ugly). Even then, how do you know that CREATE_COMPLETED action correlates to the CREATE that you fired, and not another? Well, actions also come with a payload, so what we ended up doing was passing a context object into the payload and plumbing it all the way down into the CREATE_COMPLETED and CREATE_FAILED actions. Being really strict about actions is a major reason why Flux has scaled well for us.




Could you explain how one might modify a Backbone+React application to follow the Flux model? One still needs models, no? Does one simply "wrap them in a store", which passes information to the UI, rather than having them come directly from the models? Or do you get rid of backbone entirely, and... then where do you store/manage/sync your data? What's the correct model for communicating with the server?


Good question. I haven't used backbone at all, and only know what I've read about it. Flux is a complete replacement for backbone as far as I can tell.

As you say, the models live in stores, so building on my other comment, you would have an ArticleStore that is responsible for providing access to and caching all of the Article objects. As a rule, if you want to mutate data, you do so by calling an action (ArticleActions.update, for example). See my other comment for how the update flow works: https://news.ycombinator.com/item?id=7721381

If you want to fetch data, you go to the store (ArticleStore.getByID, or ArticleStore.query). The ArticleStore will then call into the ArticleDAO (data access abstraction) to fetch data asynchronously, and when it returns the ArticleStore incorporates the data into its cache and "informs", which is basically a pub-sub push (the views/components subscribe to the stores they want to get data from).


Is there a reason why data fetches (e.g. ArticleStore.getByID) don't just return a promise for the return data? I'm guessing the current implementation doesn't return anything, it just emits an event for informing data loaded.

The one plus of the event approach I can think of is that if one component causes new data to load on the client (an article is updated), none of the components that rely on that article will show stale data - that is to say, it's extraordinarily difficult for components looking at the same data to ever be out of sync.


> I haven't used backbone at all, and only know what I've read about it

I find it mind-blowing: do you only use tools made inside Facebook? Do you develop these frameworks or just use them? If you don't know Backbone which frameworks did you learn of this kind? I'm curious.


I am another a developer that has never used Backbone (until couple months ago at least). Someone could assume that is because I develop trivial apps, but in reality the opposite is true.

Tools such as jQuery and Backbone seem ubiquitous on the web because they are a perfect fit for addressing common problems in the webpage/ajax/dynamic content area. However, there is a sizeable group of developers who work with rich applications, intranet portals, line-of-business apps that required significantly more structure and skeleton than Backbone/Marionette provide. This importance of using an overarching "framework" rather than a "library" increases in proportion to the team size and the code surface area.

In my case, we started with YUI and then migrated to ExtJS. This was before Backbone existed, although it would not have made a difference. In recent years we have evaluated Angular and Ember but did not find compelling reasons to migrate (for a greenfield project the choice may be different, but migrating significant apps carries a significant cost).

Both YIU and ExtJS provided everything we needed under one roof, and there was no use for jQuery or Backbone&co. The downsize of a mega-framework like ExtJS is the overhead - it is ill suited for a simple app. Couple months ago I started using Backbone & co for small isolated mini-apps, but I cannot wait to find a suitable replacement because it feels clumsy and backwards.

To be clear, I am not saying it is impossible to build complex apps primarily driven by Backbone - I know people who have done that (often to their own peril). I am saying that it is entirely possible to be a Facebook-level engineer working on complex applications and have zero experience with Backbone as it is great and solving problems you do not have.


I'm developing a SaaS application that is essentially a LoB in wolf's clothing and I went with Knockout for much the same reason, I'm not a strong javascript programmer but I rapidly realised if I stuck with just jQuery I was going to be in maintenance hell.

I've really enjoyed working with knockout (so much so I've even written "components" with it (ajax file handler a la gmails but with previews etc) and I've found it to have just enough structure for the stuff I need on the front side.

It surprises me that it's not more popular than it is but maybe there are reasons for that I simply don't know or understand.


I didn't develop React, I just use it at Facebook. I started Javascript development at Microsoft where we were using mostly our own homegrown stack on www.so.cl. When I moved to Facebook, the entire project I'm working on is in React. Since I haven't built many single page apps in my spare time, I have had very little exposure to Backbone. Rest assured the React devs certainly are familiar with most of the Javascript frameworks out there ;)


I've used Angular extensively, React and Spine.js, but I never used Backbone either. It's got nothing to do with where someone works, as far as I'm aware; I certainly don't work at Facebook, anyway.


The way I'm integrating Backbone and React that is very simple and has been working well is something like this:

    MyComponent = React.createClass({
        componentDidMount: function(){
            this.props.collection.on('request', function(){
    	       this.setState({loading: true});
            }.bind(this));

            this.props.collection.on('sync', function(){
    	       this.setState({loading: false});
            }.bind(this));

            this.props.collection.fetch();
        },
    });
Now you just write handlers to modify the collection and sync. setState takes care of triggering a render when your collection changes. As a bonus, you might want to render something different while in "loading" state.

If you don't want the component to own the collection (if you want to share a collection between multiple components), just pass it as a prop from a parent component; otherwise you can instantiate a new collection on getDefaultProps.


I believe the latest version of React does autobinding on component methods, so you shouldn't need .bind(this). There should be a warning letting you know this (unless it is different for the public version :/).


These aren't component methods so they don't get autobound -- arrow functions would of course work here though.


Ah of course, I was reading it wrong. Thanks.


Thanks! I find this very helpful. Seems like a terrific pattern.


@wingspan: Facebook's runs the exact same code as the open source version, but on master.


Ah right, I thought so. So as long as @hcarvalhoalves is using a recent version they should be fine.


Thanks for the encouragement! I'm using React to build a new project at work and so far I've been very satisfied with how much it gets out of the way.

Perhaps promises would be a good solution to your action spaghetti? They can deliver progress, completed, and error messages targeted to the place that cares about them, rather than passing context through the entire rest of the system.


We had talked about using promises at a lower level, but for actions it is very desired for them to be fire and forget. The view (component) fires the action in response to some user interaction (e.g. clicking a button). The action is dispatched, and the stores listen for that action, update themselves, and then "inform", which notifies the component that its data has changed. In the case of data mutations, our action modules end up having a fair amount of logic in them:

1) User clicks a button to favorite an object, lets say an Article 2) View (component) listens for the click handler and calls ArticleActions.favorite(articleID) 3) ArticleActions.favorite fires a preliminary ARTICLE_UPDATE action, for any stores that want to update optimistically 4) ArticleActions.favorite calls into the ArticleDAO (the data access abstraction) via ArticleDAO.update, a function that takes an ID, some updates, and two callbacks, success and error. 5) In the success callback, ArticleActions.favorites fires off ARTICLE_UPDATE_COMPLETED, along with the updated article object 6) In the error callback, ArticleAction.favorites fires off ARTICLE_UPDATE_FAILED, along with the error 7) Stores listen for the COMPLETED and FAILED actions to know when to update their data, including stores that show error notices

As you can see,the action layer itself should not really be accepting callbacks/returning promises. It might be easier, however, if the DAO layer returned promises, and this is something we have talked about migrating to.


From what I understand from your description, it seems that data access, server requests, etc. fall under the responsibility of Actions? Why not offload it to the Store?

e.g: ArticleActions.favorite fires off an ARTICLE_UPDATE action, the ArticleStore receives this and does the appropriate ArticleDAO asynchronous call and when done emits a change event to update any Views.


We split out data mutations from data access (see https://news.ycombinator.com/item?id=7721542). One reason is that multiple stores may be interested in the COMPLETE calls. One example is when you have a store that tracks which items in a list are selected; if one of the item is deleted, this separate store, say ArticleSelectionStore, needs to handle the ARTICLE_DELETE_COMPLETED event to unselect that article.


Would it be correct in saying that stores behave exactly (are) Eager Read Derivations as describe by Fowler? http://martinfowler.com/bliki/EagerReadDerivation.html.

I.e: they update themselves (eagerly, hence the name) based on changes from the data-access layer. They're able to choose themselves which transformations to do on the data in order for views/components to query them efficiently.


It's unclear to me why it's important that actions be fire-and-forget.

Is there no scenario in which you would want to show (next to the Favorite button) that favoriting failed but not care about other article-update failures?


Sure, that is absolutely a valid scenario. This could be accomplished using a context object as I explained in my first comment, something like {changedFields: ['isFavorite'], error: 'some error message'}. You'd then have some store that listens for article update failures and saves the error message somewhere, and then your favorite button view would pull the data from there.

The importance of keeping actions fire-and-forget is that data must live in the store; if actions have callbacks, the views would be using state to keep themselves updated with data that should be in the store.


What 'part of Flux' is listening to(or triggering, for that matter) the ARTICLE_UPDATE, ARTICLE_UPDATE_COMPLETED, and ARTICLE_UPDATE_FAILED events?

The dispatcher?


Yes, it is the dispatcher. ArticleActions.update basically calls Dispatcher.dispatchAction(ARTICLE_UPDATE, {article: article})


Om[1], a ClojureScript wrapper over React, solved problem 2 with Cursors[2]. Each component gets a reference into the data store, and can update its own data using the cursor almost transparently. In this case, it isn't that components declare what data they need, but their parents provide them with a cursor when instantiating them.

[1] https://github.com/swannodette/om [2] https://github.com/swannodette/om/wiki/Cursors


Don't cursors still suffer from the problem of intermediate components needing to pass on state they don't use? For example, a list of articles might have an author for each article. Then you have the following components: App > ArticleList > ArticleItem. So App will have to have a list of authors and a list of articles, and pass that down to ArticleList, which, for each Article, will instantiate an ArticleItem and also find the correct author to pass to it. Instead, what if ArticleItem just got an Article, and then asked the author store to give it the Author? ArticleList shouldn't really care about the authors.

The example is a bit contrived, but hopefully you see the problem? Especially consider if there are a couple more layers between App and whatever leaf component is rendering something,


In Om, components can still look at the global state without using a cursor if they want. You could have a top level "authors" key in the data store that the ArticleItems would have hardcoded into them. They could look up the author themselves as you describe by looking up the "authors" key in the global data state.

Alternately, you can pass multiple cursors[1] to a component. In your example, you could pass each ArticleItem a cursor pointing to the right article, and a more general cursor pointing to the author store. This would eliminate the hard coding of "authors", but it would require ArticleList to pass along some sort of "additional-data" cursor.

[1] https://github.com/swannodette/om/wiki/Cursors#using-multipl...


I use cursors in JavaScript with a flux architecture, and yes, prop passing is a problem for us. I've rationalized it as "there are more wires, but the wires are straight and bundled, not a ratsnest of spaghetti references"

https://github.com/wingspan/wingspan-cursor/blob/master/js/C...


How about passing a createArticleTag() function to ArticleList, so that access to the AuthorStore gets passed down via the closure rather than explicitly?


I'm currently using React with ClojureScript/Om and I really like working with it. However I'm never really sure how to model non-persistent ui changes. I.e. a user clicks a button that sets the display property of the target note to "block" so that more content is visible. I wouldn't want to keep this in the main store. So I'm pondering whether to store this in component local state and have the change appear with a React rerender, or whether it is fine to simply access the DOM and set a different style property? (i.e. getElementById...)


You shouldn't be accessing the DOM unless you absolutely need it (e.g. get clientHeight on a node). Use state instead:

   _handleClick: function() {
     this.setState({expanded: true});
   },
 
   render: function() {
     return (
       <div className={this.state.expanded ? 'expanded' : 'compact'}>
         <button onClick={this._handleClick} />
       </div>
     );
   }


No! Never access the DOM directly if you can help it. I forgot to mention, display things like you said are one of the only places we really use local state. A dropdown for instance, might have a state called "isOpen". In your click handler, you'd call this.setState({isOpen: true}), and in render, you'd only render the dropdown if isOpen is true.


Thanks, totally makes sense now that I think about it. I just removed the offending code and set up a proper local state for it.


> What we settled on, for the most part, is components declaring and fetching the data they need themselves, except for some small, more generic components.

How does this interact with shouldComponentUpdate? Generally it seems that when moving data out of props/state, it's harder to take advantage of the performance hooks that React gives you because you don't have the old and new data to compare when rerendering.


I meant to mention this; the helper mixin I was referring to is called StateFromStore, and it fetches data from stores and shoves it into state via setState.


Would love to see a large open source application beside the https://github.com/facebook/react/tree/master/examples/todom.... Todomvc is good, but still very small.


How do you handle multiple stores which interact with asynchronous behavior?

In the dispatcher, it looks like each callback is executed in order if you use waitsFor, but they do not wait for any aysnc behavior to complete.

Do you have stores register listeners with each other during their dispatcher-fired callbacks?


Answering my own question; you have actions call DAO objects directly, which make any async calls, which the action can then callback into and fire off another action.

Adding another question; do you try to prevent stores from knowing about each other at all?


do you just not use react state at all then? I have a similar sized app as you, we started off naively using state at various levels, over time we refactored the state higher and higher, and now we are at the point where all the state is kept only at the root of the view hierarchy (we use cursors). We're about one step away from lifting even that state out of react and into a store layer that has no react dependencies.


That's about right. We really only use state for transient display properties, mostly toggling display of some component.


> (we use cursors)

I'd love to hear some more about the design of your data store: are these cursors basically paths to data? Are they similar to Om's concept of cursors? https://github.com/swannodette/om/wiki/Cursors


Yes, like Om. Cursor is a read/write view of a subtree of an immutable tree.

https://github.com/wingspan/wingspan-cursor/blob/master/js/C...


We do still use React state, especially for view state like whether a piece of text is expanded or whether the viewer has toggled the grid or list view on a table. But as you said, most of our data is pulled into a store layer that doesn't have React dependencies.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact

Search: