A functional approach to className management in ReactJS, Part II

Marcos Hernández
CodeSyndicate
Published in
5 min readApr 15, 2017

Part II of a series. Part I is A functional approach to className management in ReactJS, Part I

This part is even more theoretical… but bear with me, here are important and interesting concepts that could help you in the future. Most of the ideas in this article comes from Rich Hickey and Kyle.

In Part I we ended up with an Object wrapping an Array. This object has some curious behaviors. First, its constructor accepts a function that ultimately is used to unwrap the value. But how do we introduce the actual value with a “static” method called of()? This is a functional pattern: Prepare a value to be treated generically with a protocol or interface, or create a Unit for a type. Furthermore, ClassList is closer to a protocol or interface than a “Classical” Object; or better: Is an object with an API that is compliant with a protocol or interface, in Functional Programming that’s said: ClassList is a Type. We’ll talk about that interfaces later in the series. We also added another method called map(); This method accepts a Function that receives the value and returns a new ClassList with a new value transformed by the function. This is a variation of another functional pattern: to Lift a function to work with our value, or Bind a function to our Type; remember that, will come handy later. Basically we are teaching, or lifting, the function so it knows how to work with our value inside our container.

An object of a certain Type that has a method map that accepts a function and returns a value of the same Type as the original object is called Functor. The native JavaScript Array is the most common functor. Finally to get our raw value back we Join (or Flatten, Flat) our container, in this case in discussion, that would be the ClassList.prototype.getClasses() method.

  • Unit ( or our ClassList.of() method) inserts a value into a container,
  • Lift (or our ClassList.prototype.map() method) let us transform the value inside our container and returns a new container with the transformed value.
  • Join (or our ClassList.prototype.getClasses() method) returns the value.

Actually, instead of inlining Array.prototype.concat() from our initial ClassList.prototype.getClasses() we could just map our list (For consistency and clarity sake I prefer the other way, though someone else could think different than me):

ClassList.prototype.concat = function(otherList) {
return this.map((lst) => lst.concat(otheList.getClasses()));
}

There is another common operation on lists (and is in the nativeArray.prototype) and is the ability to Fold (Reduce) the list to a single value. We have an Array.prototype.join() method in ES (not exactly the Join, or Flat, we talked before; mind you) that reduce the array into a single string, but that doesn’t really help because it is not a general solution. The real way to reduce, or fold, will allow us to do a lot of more stuff like optimizing this repeated calls to filter/map and better yet: learn!

Fold, or Reduce is an operation over a Type where we combine a new element out of it. Let’s try with NativeArray:

But we shouldn’t think of reduce in terms of only numeric sums, we could also do strings:

And sometimes we can get a more complicated element that we began with:

In sum, what Reduce does is accumulate in one single value the results of a repeated operation over each value of the list, the resulting single value could have the shape we want, but it has to be one single value.

Notice how we returned a function in weirdComputation. In this case we did that so we could pass matrix and keep the function pure, but in the future we are going to do the same to inject the function with external values and delay execution, both useful for composition.

Now consider this: we have a list of classes: ClassList(['btn__background--solarized', 'btn__text--solarized', 'btn__overlay--solarized', 'btn__icon', 'btn__placeholder']) and we want to add the--active modifier only to the ones--solarized. It’s not the most practical of examples and mapping over it won’t negatively impact performance or memory, but it will serve for now. We would solve this by filtering first those where the string '--solarized' is part of the class name and then mapping over this filtered list to add the --active modifier. Something like this:

ClassList.of([
'btn__background--solarized',
'btn__text--solarized',
'btn__overlay--solarized',
'btn__icon',
'btn__placeholder'
]).filter(str => /--solarized/.test(str))
.map((str) => str.concat('--active'));
ClassList([
'btn__background--solarized--active',
'btn__text--solarized--active',
'btn__overlay--solarized--active'
]);

But going over the list twice (one for the filter and another one for the map) is not performance nor memory friendly, we are repeating an operation and allocating memory twice for the intermediate lists. There must be a better way in which in one step we test if the class has --solarized in it and if so, add --active. That sounds like reducing something to create one new thing. It turns out that by reducing you can filter elements and apply a morphism to the results (map). Lets rework our example to do it that way:

First let’s implement map and filter as reduce:

In order to apply the filter and the morphism in one step let’s factor out accum.concat and inject it in each function to be able to compose. Beware, we are getting in a higher level of abstraction.

There it is! But the last part inside reduce looks very confusing, other programmer will stop a few seconds there before realizing that it’s a function composition, and another few minutes deciphering how the values pass between function. But composition is not difficult at all, let’s define it:

And we ended up with a transducer, which is a pretty big thing in Clojure and the FP world generally. And it’s a big thing because we separated the value ingestion process with the ingest() function and the different steps we need to select withfilterReduce(), and transform the data with themapReduce() function, meaning that we just declared what we get and what we needed to do, and leave the implementation details (how we get it and how we work with it) somewhere else. That makes our work easier because we can now reuse our reducers and test them individually.

Now that we know how to compose filter and mappings to reduce lists, we can change our ClassList.prototype.mapFilter() and ClassList.prototype.filterReduce() methods using just reduce, it will add a few lines of code, but it would be a good test to our understanding and in large lists it would perform better:

// original:
ClassList.prototype.mapFilter = function(predicate, fn) {
return ClassList.of(this.getClasses())
.filter(predicate)
.map(fn);
};
ClassList.prototype.filterReduce = function (predicate, fn) {
return ClassList.of(this.getClasses())
.filter(predicate)
.reduce(fn);
};
// RefactorClassList.prototype.mapFilter = function(predicate, fn) {
const ingest = (accum, x) => accum.concat(x);
const reducerFunction = compose(
mapReducer(fn),
filterReducer(predicate)
)(ingest);
return ClassList.of(this.getClasses())
.reduce(reducerFunction, []);
};
ClassList.prototype.filterReduce = function (predicate, fn) {
const reducerFunction = filterReduce(predicate)(fn);
return ClassList.of(this.getClasses())
.reduce(reducerFunction);
};

Here’s the complete code so far, at the very end of the file I made heavy use of arrow functions to shorten the length of the code, some people don’t like it but try to understand what’s happening there. Next week we’ll see a more practical example for this techniques. Until then: happy programming!

Addendum: Resources about Transducers

--

--