In this two-part series, I want to take a functional approach to building React applications.

There will be mathematical theory sprinkled throughout the series, and hopefully by the end of it you will pick up some useful techniques!

Series contents:

Functional programming – category theory in particular – teaches us a lot about program construction. Much of the tools that functional programmers reach for are very generalized, and widely applicable to various domains. It is this learn once, appy everywhere idea that I think would appeal to many folks in the business of making software. (Not to mention that functional programming is fun!)

React has many patterns that grew out of the community. I want to take a step back and view these patterns from a new angle, and see if we can formalize them with mathematical theory.

You will need basic React knowledge in order to understand this series. I will discuss concepts such as components, elements, and higher-order components (HOC).

If you want to jump ahead a bit, the repository with the final, full examples are on GitHub. Word of warning though, there isn’t much documentation, so it’s probably easier to read the posts. ;)

So let’s get started!

## React from first principles

An element in React describes what should be rendered in the UI.

These elements are immutable since you cannot modify them after they are created. So what can you do with immutable elements? Pass them to functions of course!

Let’s look at three pure functions that take in an element and outputs another element. We say that these functions are pure because they do not have any observable side-effects – just data in, and data out.

A bit of theory: The type for an element is `React.Element`, which are objects in category theory. Other examples of objects are `String`, `Boolean`, etc.

The pure functions `uppercase`, `clap`, and `emphasize` are morphisms in category theory. A morphism is a mapping between two objects -- in this case `React.Element -> React.Element`.

We can also create computations that allows us to capture data in our elements.

Nothing fancy here. Just a plain old functional component in React. You could render it via JSX `<Greeting name="Alice"/>`, but I prefer to treat it as a normal function for now – `Greet({ name: 'Alice' })`.

Now we’re ready for our first contrived example!

This will render “👏 HELLO ALICE! 👏” on the screen.

The function `contrivedEx` has two responsibilities:

1. Run the computation `Greeting` to get an element out.
2. Map the resulting element through multiple functions, then returning the final result.

Let’s formalize these two concepts.

## Look at my shiny new box!

We can wrap the original `Greeting` computation in a box, which I will call View.

To get the value out of the box, we just need to fold it.

As you can see, the `fold` function let’s us extract the value out of the box.

Hmm, but now when we want to map over our elements, we have to keep folding it down. This is very tedious, so let’s define a mapping function that can operate on the values within our boxes.

The `map` function will return a brand-new box that runs the original computation through the provided function `f`. Note that `map` is pure a function, and we can still maintain a reference to the original box.

All computations are delayed until we call the `fold` function. This is a really useful property since we can perform multiple transformations without needing to work with concrete values.

We can visualize `map` as follows. The morphisms can operate on values (elements), or we can bring them into the world of boxes (Views) and map from box to box. You can extract an element at any point by folding it down, but now we don’t have to until we really need it!

A bit of theory: The View box that we just create is called a Functor in category theory. You can think of functors as any object that provides the `map` function.

There are two mathematical laws that functors must obey:

1. Identity: `a.map(x => x) === a`
2. Composition: `a.map(x => f(g(x)) === a.map(g).map(f)`

We can use these laws to our advantage when composing applications. For example, our previous `superGreeting` view can be optimized by calling `map` only once, using the composition `compose(clap, emphasize, uppercase)`.

Because of the composition law, we know that `Greeting.map(uppercase).map(emphasize).map(clap)` is identical to `Greeting.map(compose(clap, emphasize, uppercase))`.

While we’re at it, let’s also make the View a Pointed Functor by allowing us to make a single value into a View using the `View.of` function.

A quick aside on `compose`. I’m using a library called Ramda that provides a lot of utilities for functional programming, include `compose`. The composition is applied from right-to-left, so `f(g(x))` is the same as `compose(f, g)(x)`. The mathematical symbol “∘” also denotes a composition – .e.g. `f ∘ g === compose(f, g)`.

Okay, moving on!

## Formalizing higher-order component concepts

So, isn’t the `View` box kind of like higher-order components (HOC)? Which we already have in “normal” React.

Here, we have two functions that when given a component or view, returns something that is like the original input, but in red.

The difference between the HOC and the View is that the latter formalizes the concept of mapping its value through the `map` function.

But wait, there is something that HOCs can do that Views cannot (yet).

For example,

The `withColor` HOC takes a color (e.g. `red`, `#fff`, `rgb(0,0,0)`, etc.), then a component that uses the `color` prop, and then returns a component that is the original, but with the `color` prop already provided.

Whereas the `red` HOC is mapping over the resulting value, the `withColor` HOC is mapping over the input props. And since `view.map` maps over the value (element), there is no chance for us to map over the original input to a view’s computation.

So is there anything we can do?

Of course! We just need to formalize this new concept.

## Mapping over inputs

Remember that the View wraps a computation defined as `Props -> Element`. Let’s take a look at a concrete example with our previous color computation.

Here, we have `Props` that is some data that contains color, and `Element` is the resulting `<span>` based on the color passed in.

What we want to do now, is to map over the input props before passing them into the computation. This can be done by defining a nifty new function called `contramap`.

Note that with `map` we call `f` with the resulting value, but with `contramap` we call `g` with the input props, and then that result is passed to the computation as input.

So, with our new function, we can do the following.

Awesome! We can now map over results and contramap over inputs!

A bit of theory: The View is now also a Contravariant, in addition to being a functor. You can think of contravariants as any object that provides the `contramap` function.

And just like functors, contravariants also have similar mathematical laws:

1. Identity: `a.contramap(x => x) === a`
2. Composition: `a.contramap(x => f(g(x)) === a.contramap(f).contramap(g)`

The main difference between `map` and `contramap` is that composition with the latter is reversed. We can visualize the difference with these two pictures

Functor composition Contravariant composition ### No need for HOCs

Now that we’ve captured both `map` and `contramap` use cases of HOCs, we no longer need them! We only need to consider whether we are mapping over result or input to decide whether we want `map` or `contramap`.

By formalizing these concepts we can be more explicit and precise about what we are mapping over, and how we are mapping. Additionally, we have mathematical laws to help us compose our program, which is awesome!

## Quick Recap

So far we’ve seen how we can:

1. Put a computation of the form `Props -> Element` into a box (View).
2. Take the value out via `fold`.
3. Map over the resulting element via `map`.
4. Map over the input props via `contramap`.

But what if we want to combine multiple Views together?

## Combining multiple boxes

Let’s step back from the world of React for a bit and consider an array.

We can see that arrays are functors since they implement a `map` function that obeys the functor laws – you can verify this for yourself!

But arrays have something our View does not: a way to combine two arrays into a new array.

So let’s add this `concat` function to our View.

A bit of theory: The View is now also a Semigroup, which are objects that provide the `concat` function.

Semigroups have one law:

1. Associativity: `(a.concat(b)).concat(c) === a.concat(b.concat(c))`

Note, that View doesn't technically obey the associativity law since the ordering of the nested `div`s are different. We will correct this later on in this post, but for now let's say that `concat` ordering is not affecting how the combined View appears on the screen.

And now we can `concat` like a boss!

Yes, the `map` and `contramap` are a bit gratuitous… But, if you run the program, you should see this. Just for fun, we can also make View a Monoid by providing an `empty` function.

A bit of theory: The View is now a Monoid, and it provides the `View.empty` function that has the following laws.

1. Right identity: `a.concat(A.empty()) === a`
2. Left identity: `A.empty().concat(a) === a`

Remember how we filtered out `null` values that result from the computation? This is why the right and left identity laws hold. Otherwise, React will generate HTML comments for `null` children, thus resulting in different markup.

## Summary

Phew! I think this is a good stopping point for this post. Let’s see what we’ve done so far.

1. We added a new “box” called View that holds a computation to generate a React element.
2. We added a `fold` function that extracts a value out of the box.
3. We added a `map` function that maps over the value in the box.
4. We added a `contramap` function that maps over the input in the box.
5. We added a `concat` function that combines two boxes together.

And along the way we learned about Functors, Contravariants, Semigroups, and Monoids.

But we are not done yet since our View still hasn’t recreated two key features of React components: context and state.

In the next post, we will replicate the React context using the Reader monad.

For more resources on related functional programming topics, I recommend the following: