Functional Composition of React Components

Functional composition is a mechanism to build complex functions by combining multiple simpler functions. This style can help you create more reusable functions in your code, reducing duplication, testing, and bugs.

A great example of designing for functional composition is the lodash/fp library. lodash/fp is described as:

an instance of lodash with its methods wrapped to produce immutable auto-curried iteratee-first data-last methods

Simple, right…? Let’s step through this bit by bit.

Immutable — These functions do not mutate their arguments, but produce new instances every time they are called.

Auto-curried — Currying takes a function which receives multiple arguments and turns it into a series of higher-order functions, each receiving a single argument. This is best explained by a simple example. Let’s say we have the function multiply:

const multiply = (a, b) => a * b;
multiply(3, 5); // 15

If we were to create a curried version of this it would look like this:

const multiply = a => b => a * b;
multiply(3)(5); // 15

Ok so it looks pretty much the same… actually it looks worse. But the benefit comes from the ability to create partially applied functions, and from ending up with a complex function that only takes a single argument. We will discuss why this is good later.

Iteratee-first data-last — If you look at a lodash function like map, it takes an array first, and then an iteratee (a function to be called on each element). It looks like this:

const doubledNumbers = map(numbers, x => x * 2);

Putting the iteratee first means we reverse the args and end up with this:

const doubledNumbers = map(x => x * 2, numbers);

But then because they are also curried, it’s actually like this:

const doubledNumbers = map(x => x * 2)(numbers);

So despite the wording, it’s all pretty simple, even if it doesn’t make sense why you would want those things yet.

So what’s the benefit?

  1. We can use these functions to create new useful, reusable functions. Let’s say we regularly want to filter negative numbers out of an array. Using regular lodash we would rewrite filter(arr, x => x > 0) in each of those places. lodash/fp would allow us to create a partially applied function filterNegative = filter(x => x > 0) and use that everywhere we need it. That’s pretty cool, although not really the biggest benefit since even if the iteratee part got more complicated you could always extract that out into a separate function.
  2. The bigger benefit comes when you start to compose functions. compose is an alias of flowRight, which creates a function that calls each of the provided functions in order from right to left, passing the output of one function as the input to the next:
const composedFunction = compose(f, g, h);
// roughly equivalent to
const composedFunction = x => f(g(h(x)));

Since a function can only return a single value, compose wants a series of functions which each receive a single value so it can chain them together. Using the auto-curried iteratee-first data-last methods, we can easily create functions that work well with compose. Let’s say we wanted to filter negative numbers, then double all remaining numbers, then add 5 to each of them. Using lodash/fp we can achieve it like this:

const filterNegative = filter(x => x > 0);
const mapToDouble = map(x => x * 2);
const mapToPlus5 = map(x => x + 5);
const f = compose(mapToPlus5, mapToDouble, filterNegative);
f([-3, -2, -1, 0, 1, 2, 3]); // [5, 7, 9, 11]

By following this functional approach, we can easily compose new complex functions using simple building blocks.

Composition in React

The way React components are designed makes it very easy to compose them. A React component can control how another component is rendered by providing props to it, and even control whether it is rendered at all. As an example, let’s make a button which logs all clicks to the console:

This component passes all props given to it down to the button, but mutates the onClick callback to add a log message. As a result, this component can be used as a drop in replacement for any <button> components in your app. Unfortunately, if we wanted to reuse this logic for other components, we would have to rewrite the entire thing. The good news it that we can make it a higher-order component and provide the component to wrap, like this:

We can then recreate the ClickLoggingButton from before by providing the component (or in the case of built in components, the string identifying the component) to the higher-order component:

const ClickLoggingButton = clickLoggingHoc('button');

Now it’s starting to look like something we could compose. We could even make this more generic and log a custom message any time any callback is called by wrapping the higher-order component in a function, like this:

And then recreate our old higher-order component and button component:

const clickLoggingHoc = callbackLoggingBuilder('onClick', 'click');
const ClickLoggingButton = clickLoggingHoc('button');

Now let’s say we wanted to log all calls to onClick and onChange. In our original implementation we would have to rewrite a lot of logic into two higher-order components, but now we can simply write this:

One important point is that we created a function which returned the higher-order component, rather than just passing in additional props with the wrapped component. We could have written it like this:

const callbackLoggingBuilder = (propName, msg, WrappedComponent) => ...
const clickLoggingButton = clickLoggingHoc('onClick', 'click', 'button');

But then we wouldn’t be able to use compose. By using partial-application, we can pre-define the other arguments and create an easily composable function which takes a react component, and returns a new react component.

A Real World Example

A lot of the time we want to get some data from a server somewhere. To do that you make a request and most likely get back a promise which will eventually be either resolved or rejected. During the loading time, you want to display some kind of loading indicator and if the request fails you want to show some kind of error. Here is a pretty typical implementation:

That’s a lot of boilerplate for every time we want to track the state of a promise. What if we try to extract it to a higher-order component? We could make a component which receives a callback that returns a promise as a prop, and wraps that callback with promise tracking logic.

Now we can recreate our old component in two parts without all the promise stuff:

That’s a lot simpler. The first component simply provides a prop to make the request, then the promise tracking component wraps that callback, and then the last component uses those promise props to render everything correctly. These components are no longer responsible for tracking the state of the promise and we can write the makeRequest method without having to worry about generic promise logic. The render method still contains a lot of boilerplate though, let’s try to extract that too.

And recreate the component again:

We have managed to remove all of the boilerplate from our Example component and recreate it by composing several simple higher-order components together. Now whenever we want to track a promise, render loading indicators, or render errors from any component we can always compose these together without having to duplicate the logic. Testing the Example component is also a lot simpler. As long as we ensure that the makeRequest method returns a promise we can trust that our generic higher-order components will take care of everything else.

One small issue here is that the loading, data, and error props are hard-coded, meaning we have to be careful about passing in other props with these names as they will be overwritten. We can always namespace these props with the name of the function being wrapped:

const newProps = {
[`${propName}Loading`]: loading,
[`${propName}Data`]: data,
[`${propName}Error`]: error

Or we could simply provide the names of the new props as arguments to the function. Either way, by doing this you can avoid issues regarding pre-existing props being overwritten.

For a more complete example, check out this JSFiddle. By default network requests are made to fail 50% of the time to test the error handling, so if you see the message ‘something went wrong try again’ click the link to retry.

If you think this technique is useful, check out my previous post explaining some more benefits of constructing your app out of higher-order components.