Maybe I should have used mobx

For khadga, this is the first time I used redux in an app, and I am starting to question why it is so popular. Having used rxjs quite a bit, as well as RxJava, I’m really questioning how and why redux became so popular. Is rxjs that much harder for people to learn?

Maybe my background in FP helped me understand reactive and redux pretty easily. But redux, while it is functional in nature and design, has some pretty hefty disadvantages compared to Observables in my opinion. Here, I’ll talk about the basics of redux and why it’s a pain to use with react.

Redux in a nutshell

Conceptually, redux isn’t that terrible. You have Action Creators, Actions, a dispatcher, Reducers and a data store. An Action is a data type that must have a field named type, and then an arbitrary amount of data. The Action is the new data that you want to somehow transform the existing data to. A reducer is a function that takes 2 arguments: the first argument is the current data in the redux data store (all of it!), and the 2nd argument is an Action. The reason they are called reducers is because they act like the function that is passed into Array.prototype.reduce function. The data store is a unified singleton of all the data your application cares about. Finally, the dispatch is a function that takes an action and passes it to a chain of reducers.

Basically, it’s FP 101, but for Javascript. To really grok what reducers do, you need to understand what the reduce function does. What a reduce (or in other FP languages they are often called fold, foldl, foldr or accum) does is to walk through some kind of data that can be iterated over and you pass each element in this iterable to a higher order function. This higher order function takes an initial set of data and some new piece of data, and then it performs some operation to “merge” the new data with the accumulated data and finally it returns this new accumulated piece of data. The reduce then repeats this by passing in the new accumulated data and the next iterable item and keeps repeating this process until there are no more elements in the iterable.

The canonical example you see with reduce, which I hate, is summing up a series of numbers.

let scores = [5, 7, 3, 10, 8]
let total = scores.reduce((total, next) => {
	acc += n
	return acc
}, 0);

console.log(total)  // 33

The reason I don’t like this example, is it doesn’t really show you the power of what you can do with a reduce function. Let’s a more useful example of what you can do:

let gradesSpring2020 = {
	sean: [83, 90],
	john: [79, 92],
	mary: [85, 92]
}

let finalExamScores = {
	sean: 88,
	john: 85,
	mary: 89
}

let finalGrades = Object.entries(finalExamScores).reduce((total, next) => {
	let [key, val] = next;
	total[key].push(val)
	return total
}, Object.assign({}, gradesSpring2020))

console.log(JSON.stringify(finalGrades, null, 2))
/*
 *{ sean: [ 83, 90, 88 ],
 *  john: [ 79, 92, 85 ],
 *  mary: [ 85, 92, 89 ] }
 */

In the second example, we have a starting set of data called gradesSpring2020 and we have a new set of data finalExamScores. What we would like to do, is take the new data from finalExamScores and accumulate them into our gradesSpring2020 store of data. What might be interesting to note is that the 2nd argument I passed to reduce (not the 2nd argument of the function passed to reduce) is a copy of gradesSpring2020. Had I not done this, the reduce function would have mutated the gradesSpring2020 on each walk through of the data.

So, let’s see how the reduce here is similar to a redux reducer. Again, a reducer in redux is like the function that you pass into the reduce method. In this example, gradesSpring2020 would be like our data store. In a redux reducer, the first argument in your reducer is the current data in your data store. The second argument is the Action, or new piece of data that you want to “merge” into your store. Notice I didn’t use the word set. You could simply replace the current value with the new value. Sometimes however, you want to take the existing value and then do something with the existing and new value to create a final value. In the example above, we didn’t replace the data, we added the new value to the array based on the key.

In Array.prototype.reduce, you walk through each element of the array and pass the element to the callback that is passed in as the first argument. The optional 2nd argument to reduce is the starting value. If you do not pass it, the first element in the array is used. So, in redux, you are not literally walking through an array. So what passes your data to the reducer? That’s basically what the dispatch function does. However, this is generally hidden from you, so it’s just good to know, but it’s mostly taken care of for you behind the scenes.

Like the 2nd example I showed using the reduce, you need to be careful about not mutating the data in your store, but instead returning a new value. And this is where my big concern comes in.

Well, actually one of many

Redux Problems

Let’s talk about some of the problems I already see:

  • Performance due to copying data and passing in the entire state store
  • Reducers and async
  • Passing data around to connected components

Performance issues

The problem is that your reducer function needs to be pure. This means it doesn’t mutate the arguments passed in, and implicitly, nothing outside of the reducers themselves should affect the values in the data store. This means you can’t have and kind of IO in your reducer function. You can’t make a call to the file system, or make a fetch API call or anything like that (which actually leads to a second problem)

Why this is concerning to me is the impact on performance. First off, you are always passing in the entire data store. This could be huge. Because you can’t mutate the data in place, you always have to return a copy of new data. I now see why react+redux benchmarks are pretty poor. This alone is pretty bad. I guess however that javascript devs aren’t too concerned about performance. Or maybe it’s just redux users.

Yes, I know that redux.combineReducers you pass it a reducer that only takes the state it is interested in rather than the entire state. However, this is really just a helper function. Ultimately dispatch will pass the entire state at which point the state can be divied up among reducers that are combined in combineReducers

Reducers and async data

Reducers should be pure.

What if the data you need to pass in is asynchronous? For example, what if you need to make an API fetch to get some data that in turn your Action needs? Well, there’s a whole new middleware that you have to learn called redux-thunk to take care of that. So now you need to use [redux-thunk][-thunk] in your code to handle this.

Passing data around

Technically, this isn’t a redux problem, but a react-redux problem. I think it’s fairly common that one component might update state that a totally separate component needs to be made aware of so that it can update itself. In react, data is generally passed down only, from parent to direct child. A React Context makes it easier to pass data from a Parent to any descendant more easily, rather than just a direct child. But what if the components are way apart in the hierarchy? Effectively two 3rd cousins needing to talk to each other?

That’s where react-redux comes in, and yet…it’s so complicated. You need to create ConnectedComponents which basically are decorated components that create the glue between themselves and a new Provider component which now has to be the root Component. I believe this alone is perhaps the greatest complexity in getting react-redux to work.

Why not rxjs?

All these problems feel like they would go away with rxjs and Observables.

First off, I hear people saying they like that there is a single data store that the application uses. That in itself isn’t bad, but the fact that you have to pass the entire store around is what I think is a bit dumb. Why does ComponentA need the entire data store? It doesn’t. It is really only interested in the parts that it needs. In the single store model, you still have to extract out the parts you need anyway (in react-redux, the mapStateToStore function that you pass to connect is what does this).

What if a component only received the data it wanted? This is what an Observable in rxjs could do. If ComponentA needs to know what got updated in ComponentB, then ComponentB should be creating a stream of data (an Observable or Subject) and ComponentA will subscribe to this data. This kills all three birds above with one stone:

  • You only care about the data you need, you don’t pass everything around
  • You only get the data once it’s ready, async is handled out of the box in rxjs
  • Connecting components is as simple as subscribing to an Observable

The only real challenge is that last point. How does ComponentA find the stream of data that it is interested in?

Mobx?

There’s a part of me that wants to use mobx, but I will stick with redux for 3 reasons:

  • There’s a ton of documentation, helper libraries and tooling
  • I already started work using redux
  • Everyone else is using it

But man, I hate that last reason.