Home > Uncategorized > React-ions – Part 2: Flux, The Easy Way

React-ions – Part 2: Flux, The Easy Way

The second of a two-part series about React:

Catching up on Flux has been an amusing experience. It’s like reading about a dance craze in the 1950s. Instead of “The Twist”, everybody wants to do “The Flux”. People are nervously looking in the mirror as they try out the moves. They write to the newspaper agony aunt, “I tried to Flux, but am I doing it right?” They want a member of the priesthood to bless their efforts with holy jargon, and say “You are one of us, Daddio!”

You’d think someone with a software project would rather ask:

  • Did my app work, in the end?
  • Does it perform okay?
  • Was it easy?
  • How much boilerplate crap did I have to paste in from blog posts?
  • Do I feel comfortable with the complexity or did it get out of control?
  • Do I know how to extend it further without creating a mess?

And based on their own answers to those questions, they should be able to figure out whether an approach was worthwhile for them.

My executive summary of Flux is: it’s a niche approach at best. For a lot of (maybe most) dynamic interactive UI development it’s not the right choice, because it’s error prone and unwieldy without providing significant advantages.

So how did I reach this conclusion?

It’s extraordinary that of the many hundreds of blog posts about Flux, hardly any try to explain it or justify it. They just describe it, without reference to long-established patterns it partly resembles, and without clarifying why it takes the trouble to deviate from those patterns. (Even worse, most explanations give truncated examples of how it might be used which don’t proceed far enough to demonstrate its intended purpose.)

The three primary sources of information I’ve drawn on are:

Derived Data

From the official site:

We originally set out to deal correctly with derived data: for example, we wanted to show an unread count for message threads while another view showed a list of threads, with the unread ones highlighted. This was difficult to handle with MVC – marking a single thread as read would update the thread model, and then also need to update the unread count model. These dependencies and cascading updates often occur in a large MVC application, leading to a tangled weave of data flow and unpredictable results.

Holy hype alarm, Batman! The part about the tangled weave is absolutely not justified by the scenario being described. This is a very familiar situation: some data set B needs to be computed from some other data set A. How did Facebook end up with a tangled weave and unpredictable results? Did they visit the wrong wig shop?

A sensible approach would be to come up with a pure function that accepts A and returns B. If that is an unworkable technique that causes “unpredictable results”, then someone needs let the applied mathematicians know. Then hopefully they can break it gently to the pure mathematicians who will then need to rebuild their entire subject from scratch.

For an example of something that does this right, look no further than a React component, which has state and props, and if either of those changes then the render function is evaluated to generate a complete new virtual DOM tree – relevant portion of the video.

Key lesson: first, try writing a pure function. If the performance is unacceptable (in 99.9% of cases, it’ll be fine) then consider alternatives.

In the video there is an example of how Facebook chat feature got more complex as it evolved. The code you can see growing on the screen is a function that runs every time the user has a new message – it’s effectively a handler for that event. They interpret the event by dishing out modifications to several different parts of the UI that may or may not be interested in what happened, depending on their current state.

They’re right – it was a very complicated way of doing it. They weren’t using pure functions. They should do what React components do: update a single definitive model of data (the state from which everything else can be computed), and then let the other components know that something has changed (no need to be specific), so they can all recompute all their data from scratch as a pure function.

In this case, that means keep all the messages received so far on a list. When a new message arrives, add it to the list and notify anything that needs to update.

It’s a common reaction to think how wasteful and inefficient it is to do that. But the second half of the video (that half that is not about Flux) is devoted almost entirely to dispelling that belief, as the React DOM reconciliation approach assumes that there will be an insignificant cost to recomputing the entire new virtual DOM every time anything changes.

In this case, rather than going from component state to virtual DOM, we’re going from list-of-all-messages to (for example) count-of-unread-messages. The functional-reactive approach here is to scan the array of message objects and count how many have a boolean property called read that is true. On my notebook such an operation is too fast for the JS timer resolution for any realistic number of messages. For a million messages it takes 18 milliseconds.

The Root of All Evil

The problem here, as so often, is premature optimisation, which is the idea that you can achieve “high performance” by doing everything the hard way. It’s simply not true.

The single most important quality software can have is malleability. It must be easy to change without breaking it. This leads to high performance software because the best way to achieve that is to measure the performance with a profiler and make careful, valuable optimisations only to those specific spots that you have found to be genuine bottlenecks. Easy to change means easy to optimise.

If you lean heavily on pure functions this will be a huge help to you as you apply performance tricks, because you can use caching very easily. And a clear distinction between mutable and immutable data is also important because it makes it easy to know when you need to clear the cache. Again, React components demonstrate this perfectly.

The Flux approach

Instead of radically simplifying and using pure functions like React, the aim of Flux is to stick with the difficult, fine-grained state-mutating approach shown in the video, where each time a message arrives, you run a very imperative set of code that mutates this, mutates that, mutates something else, in an attempt to bring them all up to a state that is consistent with what is now known.

In other words, if you want to keep doing it the hard way, Flux might just be the approach for you.

Here’s the simplest diagram:

Those arrows are effectively function calls, but via callbacks. So the thing on the right has registered a callback with the thing on the left, so that left can call right, and the influence of an action ripples through the layers.

  • An action is a plain JS object tagged with string type property (someone loves writing switch statements!) representing a request to update some data. Think of it as an abstraction of a mutating function call on your data model. As well as type it can contain any other parameters needed by the notional mutating function.

  • Having constructed an action, you pass it to the Dispatcher, which is a global singleton(!). The dispatcher has a list of subscriber callbacks, the subscribers are known as “stores”, and they are also global singletons(!!). The dispatcher loops through the stores and passes the action to all of them.

  • Each store’s subscription callback has a switch statement (hello!) so it can handle specific action types. It makes selective mutations to its internal (global singleton) state according to the instructions in the action, and raises its own change event – each store is an event source.

  • A view is a React component that subscribes to one (or maybe more) stores in the traditional way, i.e. as an event sink. Not all components have to do this. They speak of “controller-views” (two buzzwords for the price of one) that are specific React components that take care of subscribing and then use props to pass the information down their subtree in the standard React way.

So actions carry instructions to mutate data, and they make it as far as the store, where they cause data to be mutated. The last arrow is slightly different: it is not an action being passed. It’s just a change event, so it carries no data. To actually get the current data, the subscribing React component must call a public method of the store to which it is subscribing.

The point of all this, from the web site:

Stores have no direct setter methods like setAsRead(), but instead have only a single way of getting new data into their self-contained world — the callback they register with the dispatcher.

Why are stores banned from having their own setter methods? Because otherwise it would be possible to update their contents without also notifying all other stores that need to update in sync. That’s how the “derived data” problem is to be solved. If you only had one data store interested in various actions, there would be no point to any of this. (So please, if you’re thinking of blogging on this topic, remember to include in your example several stores that respond to the same actions so they can make corresponding updates to remain in sync.)

The examples

There are currently two examples in the github repository: TodoMvc and Chat.

TodoMvc has a problem as an example: it only has one store. This means it doesn’t actually have the problem that Flux is intended to solve. If there’s only one store, there’s no need for separate actions that go via a dispatcher to let multiple stores listen in. It could just have a store with ordinary methods that mutate the state of the store and fire the change event.

Chat has three stores, and is based on the Facebook chat scenario covered in the talk, so it’s got potential to be lot more applicable and illuminating.

In Chat, the three stores are:

  • MessageStore – a flat list of all messages across all threads
  • ThreadStore – a list of threads, with only the last message in each thread
  • UnreadThreadStore – an integer: the number of unread messages across all threads

The last one is more than a little ironic: if you look closely at the video, they were originally responding to events, and so decrementing/incrementing the unread message count. But in the Flux Chat example, even though they’re demo-ing a framework that is based on events so it can do exactly that kind of minimal mutation of the existing data, instead they’ve written the example so it recomputes the count by looping through all the messages (at the moment it doesn’t even cache the count).

If you’re going to be recomputing from scratch like that (and why would’t you?) then the strict action-dispatching approach of Flux is not actually going to be serving any purpose. It’s just ceremony. You could just have primary stores that store data; they’d have simple methods you could call to make them (a) mutate their data and (b) fire their own change event. Then you could have secondary stores that recompute their data in response to change events from other stores (both primary and secondary).

The UnreadThreadStore also gives us a demonstration of a dispatcher function called waitFor. This gives stores control over the order in which stores handle actions, essentially by telling the dispatcher to run specific stores’ action handlers synchronously for the current action. The reason a store will do this is because it wants to read data from those other stores, and it needs to do this after the other stores have updated their state.

It would make more sense for it to listen to the other store’s change event. A project called Reflux suggests doing exactly that.

I’ve seen discussions where people claimed that stores listening to other stores’s change events, perhaps through several layers, is the kind of “tangled weave of data flow and unpredictable results” that Flux is trying to avoid. But it’s just not. A tree of chained event handlers is a fine example of clean composition. If the arrangement is immutable (once constructed, a listener cannot be switched to listen to a different event source) then infinite loops are impossible.

The mess Facebook originally experience was not due to chained event handling, but due to disorganised fine-grained mutation of lots of different states in a single event handler.

Through the use of waitFor, Flux effectively does have stores listening to other stores change events. But it’s worse than that, because notice how in the UnreadThreadStore it has to listen for the two actions that it knows will cause the ThreadStore to change its data:

case ActionTypes.CLICK_THREAD:
    UnreadThreadStore.emitChange();
    break;

case ActionTypes.RECEIVE_RAW_MESSAGES:
    UnreadThreadStore.emitChange();
    break;

If someone changes ThreadStore so it responds to a new action by mutating its state, that someone will have to check all the other stores to see whether they also need to respond to the new action, because these other stores may depend on the state of ThreadStore which is now changing more often. If the other stores just listened on ThreadStore‘s change event this would not be necessary. It would be an internal detail of ThreadStore, and the rest of the system would be isolated from that detail. By following the Flux approach, you are encouraged to spread around knowledge of how every store responds to actions. The intention is to maximise “performance”, but to reiterate, React itself does it much more simply: if any state or props of a component changes in any way, the whole component’s virtual DOM is re-computed by a single render function.

And to be clear, there is only a purpose to any of this if you are doing fine-grained mutation of stored state in response to the same actions in multiple stores, which UnreadThreadStore clearly doesn’t do.

So enough about UnreadThreadStore. How about ThreadStore and MessageStore? Again, they are peculiar. If you run the Chat demo site, you’ll notice that there is no page where you can see all the messages regardless of the thread they are in. What you see is a list of all the threads, and the messages in the currently selected thread.

It’s strange therefore that MessageStore maintains a list of all messages across all threads. It’s even stranger that it then has a function that, on demand, filters that list to get a list of just the messages for one thread! Again, this is the right way to do it, but it makes a mockery of the action-dispatch approach.

So ThreadStore is our last hope, and it delivers! It is actually another store that responds to some of the same actions as MessageStore and mutates its own data. Hurrah! But again, something really weird has happened. There’s a function getAllChrono that recomputes, from scratch, every time it is called, a sorted list of all the threads. And that’s the function that the associated React component calls so it can display the list.

Let’s consider some simpler alternatives:

  • One store that stores all the messages in a list. When you want to know the list of threads, scan through all the messages and gather that information on the fly, ditto the count of unread messages. These can be methods on that store.

This would probably be fine, even with absurdly large numbers of messages. But it might be both clearer and more understandable (as well as “faster”, though not meaningfully) this way:

  • One store that stores threads, which each have a list of their own messages. When a new message arrives, find the thread object and add it to that thread’s list of messages. This means you can now update and keep cached aggregate information about a thread. When you need to recompute it, you can scan just the messages for that thread.

But there’s a problem with both of these approaches: they don’t demonstrate Flux at all! They can have ordinary methods that mutate the single “source of truth” data. No need for actions or dispatchers.

It is true that both Todo and Chat are contrived examples – in fact there is a comment to that effect on this issue. And so we can expect there to be some unrealistic usages; they wanted an example that was simple to follow but which exercised all the key APIs.

However, this does mean that the creators of Flux have yet to provide an example that needs Flux. And in the various 3rd party examples I’ve looked at the situation is typically worse, in that most don’t even have multiple stores.

What do I conclude from this? I’m openminded enough that I would still be interested to see an example that really does something that would genuinely be harder to accomplish (and evolve further) without action-dispatching. But I suspect it would be a very niche, unusual application.

Advertisements
Categories: Uncategorized Tags: ,
  1. March 21, 2015 at 7:38 pm

    I think you’re diving way deeper than Flux creators 😉
    I have the feeling they created Flux having only one project (Facebook) and one exact dataflow model in mind. I wouldn’t search especial wisdom there because those constant madness in original Flux proposal is enough to decide about quality of the last for yourself.
    Flux is just a rough draft and they already abandoned it and make new architecture called Relay. There is no Dispatcher at all and they add some new magic layer as I understand…

    • earwicker
      March 22, 2015 at 10:50 am

      I looked at what little info there is about Relay/GraphQL but it’s still too vapourware to reach any solid conclusions. Like React’s JSX it has another new syntax. On the plus side, at least this time they’re using ES6 templates to embed it. But on the downside it looks (again) totally unnecessary, very similar to JS object literals, so (again) I’m suspicious about whether it has any real purpose besides marketing.

      One theory I have is that FB are just putting this half-baked stuff out quite randomly, to make the rest of us waste our time trying to figure out the meaning of it, and just laughing at us all, as they watch. I’d honestly put a probability of about 1/5 on that theory.

  2. March 24, 2015 at 6:51 am

    I would bet on “more work – more salary” theory. Just recall how they reimplemented entire immutable layer where there was very good one already (Mori). Why did they do this? Why not helped to improve Mori? Well, because…

    • earwicker
      March 24, 2015 at 11:47 am

      Specifically regarding Mori vs Immutable.js, I certainly don’t have a problem there. Immutable containers are a great idea, so the more competing implementations there are, the better they will all try to be.

      Also (as always with competition) when you look closer they are targeting different niches. Mori is straight out of Clojure, whereas Immutable uses names more familiar to JS users (push instead of conj, concat instead of into, delete instead of dissoc). Also Mori doesn’t put methods on the containers themselves. So they’re tilting in different directions.

      (Also Immutable is TypeScript-ready, so that’s a selling point for me!)

  3. March 24, 2015 at 6:52 am

    And I agree with other things you said.

  4. April 2, 2015 at 3:04 pm

    Totally agree with your thoughts.

    Six months ago have same perplexity about actions:
    http://stackoverflow.com/questions/27190851/why-should-i-use-actions-in-flux
    And it was unanswered.
    Then I found a solution that works much better for my work-project. It breaks FLUX at the root.

    Now I investigate more functional solution.

    Have found, that “store” equals “cache” for many purposes. So, it can be helpful to think about store as cache, and apply some ideas from organization of cache. Mainly maintaining relevance of data, also prefetching and etc..
    Also “store” same as “property” in FRP meaning (bacon, kefir, etc.). So, It declaratively changes from another actions (=EventStreams) and stores; it stores some value; and it exports itself as observable.
    So, you can simply implement “FLUX” with FRP libraries. And you will get much, much more, than plain Flux stores.
    One incompatibility between Cache and Property is that usually FRP-libs makes property inactive, so It will loose any values when it’s not displayed. It just first obstacle in implementing Cache in FRP. But it can be simply fixed by wrapper for properties, that will make it always active. (probably always active will be in kefir.js https://github.com/pozadi/kefir/issues/43 )

    Also found article with same thoughts as mine: http://futurice.com/blog/reactive-mvc-and-the-virtual-dom/
    But personally found React faster and simpler than Virtual-dom.

    Can’t wait to try new approach in working project. Chose kefir, react and ramda.

    There was only a problem with backend interactions. It’s strangely ignored in Flux. You can read about it only from stackoverflow or blogs.
    Most libraries that interact with backend either simple ajax wrappers ($.ajax, superagent) or overwhelmed solutions (meteor, pouchdb, etc.). Still not found complete description on how best to implement it.
    In theory it’s simple. But in practice the obvious solutions leads to troubles.

    Really dislike, how marketing mess with dev. All this stuff JSX, FLUX, Relay – they introduce it. And everyone interested begin whooaaing, but nobody understand real meaning.

    Funny thing that Angular 2 is totally repeat React idea. And heard rumors that Ember also will be same in many approaches.

    • earwicker
      April 2, 2015 at 10:40 pm

      I loved your comment under the SO answer “Occam would be unhappy”. As the answer said, action-dispatching’s primary purpose is to allow mutation of multiple stores. And as you’ve found, that’s generally an unnecessarily complex way to write an application. And Facebook have found this too (judging by their example apps).

      I agree that FRP is the answer for most apps, but it can be achieved with a simpler programming style, demonstrated by Knockout’s support for computed observables. I’m hoping to do a blog post about this soon. I’ve extracted the required parts of Knockout into a library for this purpose: https://www.npmjs.com/package/kor

      • April 3, 2015 at 7:31 am

        Best advantage of “kor” is simplicity and autotracking dependencies.
        Bacon is like many extensions for observable. With bacon you have almost everything as in kor, but with many shortcuts.
        All this shortcuts helps code to look much better.

        Also in kor it’s impossible (at least simply) to implement some methods like `merge`, `concat`. And many of methods can be implemented in kor only with bunch of code.

        Simplicity helps at first. But then it restricts you.

        Also I have a question on performance. When you implement FRP with kor, probably you will have many observables. Much more than with imperative code. So performance matters.
        Almost sure that kefir.js will be incomparably faster.

        Right tools for right things. You can only mimics FRP in kor (e.g. `notify: ‘always’` for EventStreams).

        However, kor is very interesting. I intended many times to do the same.

        Kor vs Bacon similar to lodash vs Reduce (native) for transforming arrays. Reduce is simpler, and you can do almost the same. With lodash it looks at first glance more complicated. You should know about all the methods in lodash. But after the time you notice that code more readable, short and less buggy.

  5. Puneet Bhasin
    April 9, 2015 at 2:09 pm

    Have never written any React code. But this is very through investigations into it. Thanks for that! Gave me excellent insight.

    I quite like the composability using the Durandal framework which uses knockout for databinding.

    The developer of Durandal has come up with another framework called Aurelia, which replaces knockout but has same composability patterns. Was wondering if you had a look at that.

  1. March 24, 2015 at 12:31 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: