Putting everything in one map and naively rendering it is a pattern that comes from the clojurescript subset of the React community and it works because the cljs wrappers assume cljs' immutable datastructures and all implement shouldComponentUpdate for you.
React isn't magically fast, it's just O(n_size_of_diff). Getting it to perform well here requires you to figure out how to prune the size of the diff. You're doing it by splitting the app state into finer grained trees (each tree is pruning the branches it's not listening to) but any gains you're picking up from doing this could be done on the single root solution with appropriate shouldComponentUpdate methods.
The alternative (and easier) way of reducing the diff size is to simply window your data before passing it into the render, which can work for things like grids and lists but doesn't sound like your problem.
You also need to occasionally reshape your data to make the diff smaller. I work on a search app that thrashed for 1.2s every time new search results came in. The problem was that the results were invalidating a recursive menu component which was re-rendering 4 times per update. Avoiding the invalidation fixed the specific delay but the menu was still slow. Switching the menu's data organization from at tree of objects with `open` attrs to a static tree + open path and only rendering the visible nodes solved the perf problems in the app.
Getting it to perform well here requires you to figure out how to prune the size of the diff.
The most efficient way of doing that is by never doing an unnecessary diff in the first place.
Everything after that is a trade-off between convenience and overhead. Sometimes it will be worth incurring some overhead for greater convenience, sometimes it won't.
You're doing it by splitting the app state into finer grained trees (each tree is pruning the branches it's not listening to) but any gains you're picking up from doing this could be done on the single root solution with appropriate shouldComponentUpdate methods.
But shouldComponentUpdate is the wart that makes the whole abstraction leaky. The assumption that such tests have negligible cost is essential to the assumption that React used in the style you're advocating is efficient, but with non-trivial data models that won't necessarily be the case. Even if that assumption holds under any particular set of circumstances, you still now have two sources of truth, with more code to write and more scope for introducing errors.
At that point, it seems you're not necessarily any better off than you would be with a more traditional, event-driven architectural style, you're just making different trade-offs. For example, given a parent-child relationship between components, rendering top-down using React might avoid some of the need to co-ordinate updates if underlying data is appearing or disappearing. On the other hand, it might also mean writing a bunch of boilerplate shouldComponentUpdate functions just to keep rendering efficient.
My assumption was that writing the handful of sCU I think you'd need is simpler than handling coordination. You seem to have a reasonable understanding of the tradeoffs.
> At that point, it seems you're not necessarily any better off than you would be with a more traditional, event-driven architectural style
Not having to implement the dom state updates and rAF update batching are fairly big wins. The only other way I know of doing this is via data binding and the difference there is that individual updates are more efficient but establishing the bindings is usually O(n_size_of_input) and in practice every app I've worked on that did data binding eventually broke the approach. Having the shouldComponentUpdate escape hatch has allowed me to push much larger volumes of data through the system without it breaking.
I've implemented a number of reasonably complex apps but the two largest have been on immutable datastructures where React's assumptions always hold unless you screw something up. The largest non-immutable app I've written was a 25k LoC layout builder where the two shouldComponentUpdates for vdom pruning were pretty straightforward.
Not having to implement the dom state updates and rAF update batching are fairly big wins.
I agree, and these are two of the main reason we're interested in React. We've had in-house code doing some similar things for a long time, more the batching than rAF in our case, but for much the same reasons. Now that mainstream libraries are offering similar tools and with reasonable trade-offs in terms of functionality offered versus level of dependency, there's not much reason to maintain our own in the next generation of the UI.
In case you're interested, our model code also presents its interface in the form of immutable data structures, with quite fine-grained events available to monitor quite specific parts. But even with that, in something like our small multiples test case, where we're plotting a number of interactive SVG charts using modestly derived data but thousands of underlying data points, it appears that the indirections and tests do still add up to a noticeable level of overhead. Of course that's more demanding than what a lot of web apps will ever need, and our conclusions for our project won't necessarily be anyone else's conclusions for theirs.
React isn't magically fast, it's just O(n_size_of_diff). Getting it to perform well here requires you to figure out how to prune the size of the diff. You're doing it by splitting the app state into finer grained trees (each tree is pruning the branches it's not listening to) but any gains you're picking up from doing this could be done on the single root solution with appropriate shouldComponentUpdate methods.
The alternative (and easier) way of reducing the diff size is to simply window your data before passing it into the render, which can work for things like grids and lists but doesn't sound like your problem.
You also need to occasionally reshape your data to make the diff smaller. I work on a search app that thrashed for 1.2s every time new search results came in. The problem was that the results were invalidating a recursive menu component which was re-rendering 4 times per update. Avoiding the invalidation fixed the specific delay but the menu was still slow. Switching the menu's data organization from at tree of objects with `open` attrs to a static tree + open path and only rendering the visible nodes solved the perf problems in the app.