Resisting the Higher Order

Written by zhirzh | Published 2018/06/27
Tech Story Tags: javascript | reactjs | functional-programming

TLDRvia the TL;DR App

Go Functional!

So the big idea is that although React itself is declarative, vast majority of code that goes into making an app is imperative. This can be fixed (if you think it needs fixing) by mixing in more functional patterns.

Rendering data

What is react?

A JavaScript library for building user interfaces — reactjs.org

Although React is for building data view (or user interfaces), it must mingle with data, in some form, at times. And when it does, almost always the data is tightly coupled with the the interface code.

Wouldn’t it be great if we could just pack our views in a separate box and just sprinkle the data as and when needed?

A box called “Comp”

This code is from the talk “Oh Composable World!” by Brian Lonsdorf. If you haven’t seen it yet, I encourage you do so before continuing.

Oh Composable World! @ 21:40

If you haven’t seen the video, here’s a brief explanation of the code you see:

The Comp function takes in a react component (as the argument g) and returns an object with methods fold and contramap.

The fold method is the original component passed to Comp. This means that calling fold is just like calling Foo or rendering <Foo />. After all, the following expressions give equivalent values.

  • <Foo fizz="buzz" />
  • Foo({ fizz: "buzz" })
  • Comp(Foo).fold({ fizz: "buzz" })

The contramap method is there to modify props. It takes in a function f that takes in some value and returns another value. The returned value is then fed into the original react component for rendering as a prop.

We can say that contramap transforms the input to the component.

Modifying the output

Modifying the output means to modify JSX. You may be thinking of a higher order component. But here’s the problem — HOCs don’t work with JSX.

Concretely, a higher-order component is a function that takes a component and returns a new component. — React docs on Higher order components

With the help of flow, if I create types for both Component and HOC, I get:

  • type **Component** = Props => Element;
  • type **HOC** = Component => Component;

It isn’t immediately obvious, but the type signature for an HOC is pretty long:type **HOC** = (Props => Element) => (Props => Element);

What we want is fairly simple: Element => Element.

The map function

This is what the map function is used for. If contramap deals with a component’s input (props) then map deals with its output (JSX). The function f takes some JSX and wraps the original component’s output in it.

Safety First

Before venturing any further, we need to make the code safe — type safe.

Here are the basic flow types:

The important thing to observe here is that the type Component is a mapping from type Props to type Element.

Armed with types, we can now annotate our Comp function’s return object. Please note that I am using Flow’ [$Exact](https://flow.org/en/docs/types/utilities/#toc-exact) utility type to make the intent clear that type Box is an object with fixed keys and values.

If we really think about it, we can see that the function Comp is a mapping from type Component to type Box.

If you’re not a fan of arrow functions, you can rewrite the same thing as a function declaration. I prefer the current system because I can decouple my types from my code. IMO, that’s much cleaner.

Writing Comp using function declaration means we’d have to integrate the types into the function definition.

JSX Pipeline

Right now, every Box object sits in isolation from the rest of the world. They can’t even connect with each. But if they could, we’d have an extensible JSX pipeline at our hands.

The concat function

Conceptually, the concat function merges two entities of the same type. We should also add a concat method to our Box type.

With the help of our new friend, we can now join multiple Boxes together.

Nasty concat

It works nicely but there’s a small hiccup. Everytime concat is called, it appends a new <div /> into the markup. And that’s nasty.

An array equivalent would look something like:[1].concat([2]) === [1, [2]]

But we know that’s not how concat works on arrays. It works like:[1].concat([2]) === [1, 2]

This means that array’s concat method is associative:[1].concat([2].concat(3)) === [1].concat([2]).concat(3) === [1, 2, 3]

But our concat is not associative:

Nice concat

The easy way out is to wrap the result of concat in an array, since arrays already have a nice and friendly concat method, and also because React 16 added support for rendering an array of JSX elements. But doing so means rewriting the Comp function. Besides, it’s already been covered in “Deconstructing the React Component” by Jack Hsu.

Instead, we will use the [<Fragment />](https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html#what-are-fragments) component. If you’re feeling adventurous, you can use the updated syntax for fragments.

With such a minor update to the code, we’ve solved two problems:

  1. We no longer need a separate <div /> to wrap other JSX elements
  2. Our concat is now associative. Huzzah!

Conclusion

With the final addition of a concat method, we can take a step back to admire the beauty of our code.

But why?

Without going any further than this, it’s best if we talk about the merits of our current system. In the article “React Higher Order Components in depth”, Fran Guijarro has outlined numerous use cases for HOCs and split them up in two classes of implementation — Props Proxy and Inheritance Inversion. We’ll look at each of them and see what we can do about them.

Props Proxy

This is the more conventional usage of an HOC and it allows:

  • Props manipulation
  • State abstraction
  • Accessing the instance via Refs
  • Wrapping the WrappedComponent with other elements

Props manipulation and **State abstraction**I’ll have you know that there’s yet another pattern that deals with this specific usage of HOCs. It’s called Render Props and you can read more about it in my other article “Exploring Render Props”.

Let’s look at some sample code that uses a HOC to implement both usages:

Before we can make the code functional, we need to convert it to the render props pattern.

We can now functional-ise the code. First, we pack <Num /> and <Foo /> in separate Boxes. Since Num is a class, we also need a function to convert ES6 classes into functions. Luckily, Brian Lonsdorf got us covered.

And now, we define our <App /> in terms of Boxes.

And that’s it. Two HOC uses down, a few more to go.

Accessing the instance via RefsTypically, this is how we use refs in HOCs:

Which is, under the hood, just a nested component:

And the functional equivalent is:

Wrapping the **WrappedComponent** with other elementsOkay. This one is pretty obvious. We just wrap one component in another. This is exactly what the map function does:

Inheritance Inversion

We have covered the entirety of what an HOC can do by acting like a component pass through. If you thought that was interesting, just check out how to implement II — it’s bound to confuse you. An HOC that returns a new component that extends WrappedComponent.

This is the one thing that we can’t do with with Boxes. We cannot have inheritance amongst functions and functions are a major participant in our functional pattern. And this makes me sad 😔

The End

… for now. I hope you found this bit interesting and possibly even helpful. Now we can explore data flow in our functional react pattern. But that’s an affair for another time. The code for this post is on repl.it.

If you want more, here’s a list for you:

Addendum: Class components

So far, we’ve only looked at functions and ignored class components. Let’s invite them to the party as well. Since Comp can only handle functions, we will need a helper function that converts classes to functions.

That’s all we needed to do. Seeing how simple of our helper classToFn is, can you make a guess at its type? Made your guess? Ready for the answer?

Addendum: Adding stricter types

Once you’re done with this article, I suggest you take a look at this article to better explore the landscape of type safety that comes with this kind of setup.


Published by HackerNoon on 2018/06/27