Redux Considered Harmful
Facebook engineers had a problem. They could not keep straight an “Unread Messages” counter in their chat application. They would get it working, but it would break when the next chat-related change was made.
In despair, they created Flux. tl;dr? Take all the individual bits of state scattered across our web application and:
- gather them in one place…
- …organized in a nice, clear schema conceived independently of our messy screen jumble; and
- tightly constrain how those neatly organized bits can be altered by the messy screen code, by limiting change to the execution of pre-defined “actions”.
Whew! I feel better already.
But something went wrong. Not even Facebook likes it.
“The Flux project is in maintenance mode and there are many more sophisticated alternatives available (e.g. Redux, MobX) and we would recommend using them instead.” — Facebook
The weird thing? Redux and MobX are polar opposites when it comes to state management. MobX manages state in place, right where Facebook engineers were unable to manage it. MobX solved that with good old TFRP. tl;dr: MobX figures out how to manage the counter itself, by watching how it gets calculated.
Redux is lipstick on the Flux pig, continuing the exile of state into “stores” isolated from the web application, but this time into only one store. They make other refinements, too, but…something went wrong. Boilerplate.
Ever watch a bunch of students setting up a zillion dominos? That is Redux. How bad was it? They had to make a new Redux to fix it. Why do I have a bad feeling about this?
The Real Problem
What went wrong with Flux, that no amount of Redux (or Vuex or CLJS re-frame) can fix? What is the mistake that MobX (and Matrix) avoid?
The answer is too easy: they forgot that our the goal was to build an app.
Recall that this all started because Facebook engineers had a problem displaying a chat counter in a big Web app. They were not alone. Big web apps have lots of state in many varieties, a lot of ways for that state to change, all of them arriving asynchronously, and most of them coming from that most random of randomness generators, a human.
And they had a plausible solution: gather all the state in one place, neatly organized like a third-normal form relational database, and strictly regulate how that state gets updated.
But look at what they did:
- pull the state away from the components of the app we are building;
- store it under a schema different from the structure of the app we are building;
- force the app we are building to figure out at every turn where to find the state it needs to show us poor users what we want to see; and
- force the app we are building to figure out events and actions that will make the changes we uses want our app to make.
The Flux pattern of isolating app state in a separate store began by creating semantic and architectural dissonance between application and state. They achieved correctness, but someplace other than the app GUI structure. Left as a soul crushing, menial exercise was setting up and living with the brittle domino runs, and hand wiring the app back to the state.
And it was never necessary. None of it.
“In place” state management
When Dijkstra brought the curtain down on the GOTO statement, he had support from fancy proofs that any program with GOTOs could be written without them, using only structured programming constructs.
In the case of the Flux pattern, we have many existence proofs that even messy GUI applications state can be managed without exiling that state in a separate store. The first your author knows of is the Common Lisp KR knowledge representation system, and the newest is the aforementioned MobX and Matrix libraries. In between we have Scheme FlapJax, Python Trellis, ClojureScript Hoplon/Javelin, and others.
These all avoid any semantic dissonance because they manage state in place, and they involve no boilerplate, because they do their work transparently, leaving the app developer to work on features for their users.
The larger moral, for kids watching at home
We promised to return to Facebook’s blind spot when it came to MobX being the polar opposite of Flux. Here we are.
We have said Flux was a plausible solution, but the boilerplate was murder. And yet they and others have persisted with Flux. The message to young programmers everywhere:
Don’t be that engineering team.
Someone challenged the writer about my Flux takedown. Are you sure you understand why they did what they did? I answered simply:
“Sure, I might have done the same. It is not a bad idea. But I would have stopped when I saw the boilerplate.”
In my case, faced with a related problem, I happened to start at a different corner of the same puzzle and, like many other reactive engine developers, come up with point-to-point in place state management.
Facebook engineers, and the many who have come along since and copied them, could have found “in place” state management, too, had they not clung like bulldogs to separate stores after the boilerplate surfaced.
Kids watching at home: do not get married to even your best solutions. When difficulties arise, do not rush to cure them. Listen to what the difficulties are saying.
They might be saying, Stop. Turn back. Wrong way.