5 ms·
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
by wingspan 12y ago
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.
- couchand 12y agoThanks 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.
- wingspan 12y agoWe 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.
- EdwardMSmith 12y agoWhat 'part of Flux' is listening to(or triggering, for that matter) the ARTICLE_UPDATE, ARTICLE_UPDATE_COMPLETED, and ARTICLE_UPDATE_FAILED events? The dispatcher?
- wingspan 12y agoYes, it is the dispatcher. ArticleActions.update basically calls Dispatcher.dispatchAction(ARTICLE_UPDATE, {article: article})
- deleted 12y ago[deleted]
- zzzaim 12y ago
- rattray 12y agoCould 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?
- wingspan 12y agoGood 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 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).
- riquito 12y ago> 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.
- e1g 12y agoI 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.
- arobbins 12y agoOm[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 https://github.com/swannodette/om [2] https://github.com/swannodette/om/wiki/Cursors https://github.com/swannodette/om/wiki/Cursors
- wingspan 12y agoDon'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,
- dustingetz 12y agoI 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/Cursor.js https://github.com/wingspan/wingspan-cursor/blob/master/js/C...
- skybrian 12y agoHow about passing a createArticleTag() function to ArticleList, so that access to the AuthorStore gets passed down via the closure rather than explicitly?
- arobbins 12y ago
- terhechte 12y agoI'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...)
- wingspan 12y agoNo! 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.
- terhechte 12y agoThanks, totally makes sense now that I think about it. I just removed the offending code and set up a proper local state for it.
- underwater 12y agoYou 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> ); }
- dustingetz 12y agodo 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.
- wingspan 12y agoThat's about right. We really only use state for transient display properties, mostly toggling display of some component.
- jingc 12y agoWe 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.
- modarts 12y ago> (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 https://github.com/swannodette/om/wiki/Cursors
- dustingetz 12y agoYes, like Om. Cursor is a read/write view of a subtree of an immutable tree. https://github.com/wingspan/wingspan-cursor/blob/master/js/Cursor.js https://github.com/wingspan/wingspan-cursor/blob/master/js/C...
- spicyj 12y ago> 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.
- wingspan 12y agoI 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.
- shaohua 12y agoWould love to see a large open source application beside the https://github.com/facebook/react/tree/master/examples/todomvc-flux https://github.com/facebook/react/tree/master/examples/todom.... Todomvc is good, but still very small.
- kaonashi 12y agoHow 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?
- kaonashi 12y agoAnswering 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?