Flux Architecture Visual Cheatsheet
danmaz74.me
danmaz74.me
In my experience, the toughest thing to grasp about Flux was how to handle async server actions. It's something that a lot of tutorials (including, unfortunately, this graphic) handwaves, but it's one of the first things you need to nail down if you want to do anything exciting in an SPA.
The todos usually make it seem like you have to to have this flow of information:
ActionCreator -> ApiUtil -> ActionCreator -> Store
...but if you use the same action creator, you end up with a circular dependency. So you actually have to create a separate file of ServerActionCreators, that are only called with ApiUtils:
ViewActionCreator -> ApiUtil -> ServerActionCreator -> Store
This seems like a lot of boilerplate. At my job, we've simplified this a lot by using Reflux, which has async actions that run one Store callback when a call gets initiated, and another related one when it gets completed. But it's not ideal.
Personally, I'd rather see a bigger app than a TodoMVC implementation with a "correct" example of async server actions.
Create action -> action[dispatch 'action pending' -> async action -> dispatch 'action complete with data'] -> stores.
Keep your stores completely sync, and the data in a single object, and maybe have them all be pure functions, and suddenly there is no web of anything and theres a single source of truth, and bam. nice.
Dan Roam's Back of a Napkin is a good guide to thinking and communicating in this visual way. You can get quite a long way with the freebies on his site, but it's worth shelling out for the whole book.
http://www.danroam.com/the-back-of-the-napkin/
When I studied the diagram, I found myself running through an imaginary scenario in my head. The Overview text makes it hard to do this. If you don't create a diagram and want to explain a process clearly, don't explain the components -- follow an example action through the process, and your readers will grasp it much better.
Specifically, I'm not sure what problem the Dispatcher is actually solving. It seems to just add boilerplate and indirection for little benefit.
I even think it could also be used for one more goal.
With standard Flux, if an action updates multiple stores, and a ModelController is dependent on those multiple stores, that ModelController will get rendered (to the virtual DOM) multiple times, which is inefficient.
If instead the stores notified the dispatcher that they were updated, and then it was the dispatcher to dispatch all the "change" events to the ViewControllers only once (ie: if a ViewController is dependent on two different stores, and they were both "changed", it would still only get one "change" event) this would make those situations much more efficient.
If anyone knows about a flux implementation that solves this - I'd love to hear about it!
It is rather immature, but we are using it in production without a problem, and the API is stable as of the 1.0 RC.
In reflux, it was never clear to us whether certain async server operations should be handled in stores or in the actions. In general, you would always want them to be with your actions. Though every time you need a store variable to do the server call, you end up putting code in the store again (or passing the needed state through your react components, which is even uglier).
Not so in redux, where you have access to the stores in your actions. This way, stores get really dumb, which is the way they should be IMO.
And, btw, replacing reflux by redux is relatively easy and straightforward, as the same general concepts (flux) apply.
I also find the redux code easier to read, as the whole library is doing less (while achieving the same).
The main idea is to think of stores as reducers (redux = reducers + flux). He also gave a very good talk on it at react-europe: https://www.youtube.com/watch?v=xsSnOQynTHs
Any video/article recommendations?
I usually wait ~1yr before trying out any new JS library/framework and wait to see if they stick around - given the constant hype machine that generates them.
One of the big things I think a lot of React/Flux tutorials miss out on are the "Smart and Dumb" components. This was the missing "view controller" that I am used to with MVC and the flowchart illustrates it nicely.
EDIT: clarification
Angular have a specific service ($http) for that.
By the way, when I did my research, I found out that some people are using React with Meteor, instead of Flux. That way, server synchronization should become a non-issue (but you need to like the Meteor way).
PS just wished mural.ly allowed to export a PDF instead of a PNG
I was just concerned about going back to the link in a couple months and finding a dead url.
One thing missing from many flux tutorials and sample applications that I would love to see included is error handling. How do you manage errors in your flux applications? Do you keep them in the store? If using something like react-router, how do you ensure you flush errors from the store as your routes change and they are no longer applicable to the data in view?
Thanks.
https://twitter.com/substack/status/621818725159710720
https://twitter.com/substack/status/621832733564628992
https://twitter.com/substack/status/621639688919515136
That first tweet succinctly explains flux in less than 140 characters.
At the end of the day, pretty much every react "best practice" I've seen converges on approaches that raynos/mercury offer out-of-the-box.
React's decision to allow local state inside components was an "original sin" and every single engineers that decides to author a library to make React/Flux simpler is really just engineering around the poor decision of allowing local state.
Have of those should, might, could update if they felt like it react class props are unnecessary complexity that had to be bolted on to overcome issues with local state.
Here's a better approach:
(1) components that are pure functions that take in state that they should render. state hasn't changed? don't call the function
(2) components that have state that needs to be tracked can export a function that produces an instance of the state that component consumes
(3) compose the state of the UI, by calling the state instantiating function of all the components in your UI. Nest as appropriate.
(4) Make sure all these state instances are bijective lenses that keep one source of truth for a certain state value. Everywhere that state is needed or could be modified receives that "cursor" via dependency injection. The simplest example demonstrating a state cursor is the raynos/observ library.
The composed state object is the waist of your app hourglass, just like IP is the waist of the Internet. All I/O modifies state and that state propagates from there. Anything rendered to the screen is the O in I/O. Any events coming from your mouse or other peripheral is the I in I/O. Any syncing via XHR or websockets can be the I and the O in I/O. All I/O flows to the state.
Anything that needs to react to changes in that state have two options: subscribe to state change events (push) or read the current state as necessary (pull).
This really isn't all that hard and React/Flux has overcomplicated things immensely and given it a flashy name. The myriad libraries that purport to make it easier and simpler are just overcompensating for something that fundamentally needs to be re-engineered, but won't because it breaks backwards compatibility and requires people to move state logic (read: business logic) out of the component classes (that probably shouldn't have been there in the first place).
I do flux and I keep everything in stores or some variation, and my components take everything as props, so they're usually pure functions. However, for my base (i.e. heavily reusable) components, I use local state to track ephemeral component state. One such example, if I'm doing an animation when someone clicks on an input, such as a material design textfield.
So in my comment above I mentioned having a component export two functions. One function is a pure render function and one function creates a state instance. The state of the animation would be one of the things tracked in the state instance:
var Observable = require('observ');
var svg = require('virtual-dom/virtual-hyperscript/svg');
module.exports = state;
module.exports = render;
function state(initialPosition) {
return new Observable(initialPosition || [0,0]);
}
function render(positionState) {
var center = positionState();
return svg('circle', {
'fill': 'rgba(255, 0, 0, 0.1)',
'stroke': 'black',
'stroke-width': '2',
'cx': center[0],
'cy': center[1],
'r': 10
});
}
The parent component would export its state in the same way. Its own state would be composed by calling the state() function of its own subcomponents. Turtles all the way down.At the top of the app, the saved state would be injected into the state of the top level component, which would decompose it into parts and inject it into the state functions of its subcomponents such as the one above.
This single composed state object tree makes it trivial to record diffs in state for playback or to serialize and save the state to localStorage or the network.
It's trivial to work with the approach above. As trivial as the local state approach of react. It's extremely consistent about where state lives. It doesn't sometimes live in a global place and sometimes live in some other place. Another developer joining the project doesn't have to hunt around for where state for a particular value is being maintained. It also makes it simple to do far more complex things with animation, like build a complex animation editing application where an animation might be controlled by knobs, buttons, sliders, dials, etc. With local state you'd have to wire up those controls directly to the internal state of a component, completely messing up separation of concerns. With the composed state object, you just write one function that maps the state of two sliders (x&y) to the state of the circle and have it listen to change events on the slider position. It's trivial to wire things up in both directions by having them listen to each other.
Time travel (undo/redo) is also trivial to implement: https://github.com/Raynos/mercury/blob/master/time-travel.js 67 LOC with generous whitespace.
You could wire up the time-travel state to a slider as well to accomplish something like this: https://www.youtube.com/watch?v=ri614C_Buwg
For the viewing state over time features in the froggato level editor you simply take the series of state over time, use it to instantiate state() multiple times and render them all in the same frame.
Show one circle:
scene -> circleRender(circleStateAtTimePosition(now));
Show one circle over time: scene -> circleRender(circleStateAtTimePosition(now - 2));
-> circleRender(circleStateAtTimePosition(now - 1));
-> circleRender(circleStateAtTimePosition(now));
-> circleRender(circleStateAtTimePosition(now + 1));
-> circleRender(circleStateAtTimePosition(now + 2));
Local state simply makes everything much harder to reason about as you move beyond all but the most trivial apps.Lastly, for long running ephemeral state animations, you gain the ability to "cancel" the animation. Imagine you have a "card" animating out from the right to show a panel, but you click on something else that would close that panel and open another one. It's much simpler to cancel that animation midway, close the first card and animate out the second card. The alternative is to add a bunch of methods to the card class to handle the cancellation logic. If you need that cancellation logic in other components you need a mixin, but it's all brittle because its tied to the key names on this.props as opposed to being able to make a animation controlling function used in an animation coordinator that merely needs to receive cursors (i.e. nothing is hard coded to key names)
You might say "but I have no reason to track ephemeral state in that way" and you would be correct, you don't need to. But there are no disadvantages of doing so relative to local state. Furthermore you gain predictability about where state lives and the ability to not worry about refactoring if one day decide that you want to now control a particular piece of ephemeral state.
Many Flux implementation lucks of isomorphic, proper hot-reloading, etc. ES6 transition is on its way. Most code will be abandoned. Alt, Reflux, etc are all in trouble.