Why you should stop using state reducers in your app to be fully reactive
With all the hype for FRP, the only way to manage state seems to be to use a stream of immutable states using reducers. Let’s break that.
Coming from a C# background, stuff that is reactive is not very new to me. Reactive Extensions for .NET (Rx) was created by Microsoft long ago, and has helped the .NET developers in a lot of occasions (at least the ones who knew about it). But the way Functional Reactive Programming (FRP) manages state, with reducers, is pretty new to me. But it is like a logical consequence of using FRP, so much so, that I could predict this is how state management will be done, as and when I encountered the challenge. It just felt natural.
When I say FRP and stuff like that which don’t resemble .NET or C#, I am basically speaking of Cycle.js apps and nothing else — I haven’t really used any other FRP frameworks, or libraries.
I find the notion that your app is a pure function that takes user inputs and gives outputs to the user in various ways, with no side effects, simply amazing.
The way Cycle.js achieves this is by pushing the responsibilities of side effects to what we call drivers in the Cycle.js world.
When I made small Hello World! apps with Cycle.js, it never occurred to me that not having access to a state variable and being able to change it would ever be a problem. But as soon as I started out on creating a Recall app with Cycle.js, the problems became all too apparent. I was going to have not just one property, but multiple properties of my app to which I needed my view to react to. In comes state and state management.
Persisting and managing application state
as a stream of immutable states
State is something that our function will change, but it is not intended to be a side effect. It can be called something that our app will make use of to render. But then, it still has to be persisted. If it is, the how will our function be pure? We’re in a sticky situation now. That’s why we have guiding principles, don’t we?
“anything that changes in cycle.js is a stream”
Hmm. That does make sense. If state is what is about to change, let’s model it as a stream instead of a mutable object. But why can’t we also mutate state, and have a stream of states? Because if change is modelled in more than one way (both as a stream and as a mutable), it quickly creates confusion. Besides,
“shared mutable state is the root of all evil”
So we take a stream of states and react to it in our app, based on user intent, and sometimes based on other things. We take an initial state, and describe how the state will be changing, using reducer functions. Reducer functions are functions that react to change, and take the current state, and return the next state, of the app. We then fold over (accumulate) the stream of reducer functions, and create a stream of states.
Recall: A little context
A sample to anatomize solutions over
It is a simple app, with a square grid of 25 squares. When a new game starts, 9 cells are selected at random, and shown to the user for a few seconds. Afterwards, the question is hidden, and the user has to select the 9 cells correctly, by clicking on them again. Once the user selects 9 cells, we compare them with our question. If he got all of the 9 cells right, he is awarded a score of 1. The user additionally has the option to reset his selected tiles during his play.
This should provide some context when we go through some code.
Using action-wise state reducers
The first thing that comes to mind may not always be the best
Taking the Recall app as a sample application to discuss things over, I first implemented what I would call action-wise state reducers. These are actually functions that take state and modify it (create new states, not mutate existing ones) and return a new state, but we declare them grouped by actions. This has a nice way of showing what all changes an action causes in your application’s state.
As an example, some time ago in its code base, we had the following code declaring reducers and the state$:
You will see that newGame$ affects a number of properties in the state. This is a place where I felt imperative programming having crept into reactive programming, as Andre explains in this wonderful talk, Dynamics of change: why reactivity matters:
If you skipped the talk, we are basically calling a couple of methods to set a few properties in the state. Calling methods is a bit imperative. Instead of the properties deciding when and how they change, we now have an action dictating what it changes and how. This kept poking at my mind — I must be doing something wrong, I thought.
Using property-wise state reducers
Sometimes even the second iteration doesn’t fit
So instead of action-wise state reducers, saying what these actions do, we can move on to having property-wise state reducers, which will help us declare what each property of the state reacts to, and how it changes the property. We are going to merge all the reducer streams anyway, and we should have the same net result. This would be an acceptable refactor of the reducers function (with a few improvements):
Are we satisfied yet? Um, I guess not. We have just expanded things out too much. It almost looks like a mess. The huge blocks of text make every reducer definition big, and you don’t understand much of the flow at first glance, even though a particular reducer stream sets only one property of the state.
Sometimes we get several states for a change, instead of a single new state as a result of the change. Our initial expectation of an action’s effect was that an action should produce only one new state, and not multiple states in quick succession. Going property-wise reducers this way would create that problem. But is it really a problem?
Also, sometimes a property depends on another property, but it’s not immediately apparent. Take, for example, the scoreReducer$:
The score property actually depends on the over property. It returns the old state without change, when the over property of the state is false. But right now we have no way to specify that property-dependency, because we don’t have a stream of over property changes to react to, we only have reducer streams, and they operate on states.
If, for example, you change the order of the merges in the return statement, by putting the overReducer$ above the scoreReducer$, the behavior of the app changes, and this is a code smell. We want to be asynchronous and reactive, not naively synchronous, depending upon what comes before what.
Moving over to property streams
Third time’s a charm: Do we even need reducers?
Let’s change things over. Let’s model state, not as a stream, but as an object with different property streams. In effect, let’s move over from action-wise state reducers, and property-wise state reducers, to property streams. You will soon appreciate the advantages that this approach offers, once you see it in action. The following will be a good refactor into what I’m talking about.
It can be seen that I actually need reducers only for score and selected cells, where I need to know the previous value in order to modify it. But the state exposes only value (or property) streams, to which subscriptions can be handled independently. And you can see that the properties which depend on other properties clearly declare their dependencies.
The reduce function is a helper to help us define reducers:
Whenever a actions.newGame$ emits an action, the puzzle$ emits an array of 9 random mutually exclusive numbers (from 0 to 24, referring to the index of the cells selected as the puzzle question). We remember the last value in case we need to access it again (for example, to do some calculations like checking the solution, later).
Whenever puzzle$ emits a puzzle, or actions.reset$ emits an action, the selectedCells$ emits an empty number array. Whenever actions.selectCell$ emits an index of the cell that was clicked, selectedCells$ emits an array with the index removed or added, depending on whether it was in the previously emitted array. This is accomplished by a stream of reducer functions called selectedCellsReducer$. We also remember the last value emitted by the stream.
Whenever puzzle$ emits, allowed$ emits a false, followed by a true after 4 seconds. This is extremely clear. This maps to whether the user is allowed play.
Whenever selectedCells$ emits the selected cells, over$ emits true when the selected count is equal to 9. If not, it emits false. However, it only emits when the value it’s about to emit is different from the previous value it emitted. This allows us to listen only to change events on the over property.
Whenever puzzle$ emits a puzzle result$ emits null, as there is no result to a new puzzle/game. And whenever over$ emits a true value, result$ emits the result of the game (now that the game is over). This won’t keep repeating, because over$ emits only changes. Cool, right?
Whenever result$ emits a non-null result, score$ emits a score that is incremented from its previous value, based on whether or not the user won the game. We use a reducer in this case, since we need the previous score. Or, maybe we could’ve set a score$ which streams the changes to the score instead. In that case, the listener of the score$ would’ve aggregated the score value, not us. This is just an example.
Finally, we return the streams (not reducers) as properties of the state. Since we don’t merge these streams, we have no problems with the order in which they are merged, in particular we are free of concerns like which stream emits first. We have also clearly defined how properties depend on other properties and actions.
Isn’t the flow too easy to follow? And, we didn’t even have to write imperative code, or use a library like immutable. We finally have fully reactive code, with greater flexibility, easier-to-understand data flow and dependencies, and code with clearer intent. In case we need a driver to react to the over$ (in order to post a tweet with a message every time, perhaps), all we have to do is:
We are not filtering a state$ to watch out for distinct over$ values, because we’re simply not concerned about anything else in the state.
Anyone who needs to listen to a bunch of values, can simply use combine in order to get the latest values from every stream. In the view, I particularly have used it like this:
What do you think?
Please share your thoughts. Also let me know if there are particular things in or about Cycle.js that you would enjoy reading further.
NOTE: The Recall app is a live app written with these principles in Cycle.js. The source is available here, and can be inspected and debugged in TypeScript, due to some wonderful sourcemaps produced by webpack.
You can actually browse the source at each stage:
action-wise state reducers
property-wise state reducers
We have since removed the Reset option because it seemed unnecessary.