Five years have passed since React was officially released. We’ve seen lots of progress up until the latest 16.4.1 version, both internally and externally, with the release of numerous relevant libraries. Now that React has been the talk of the town, listing its good points might be boring. Still, one of the advantages I want to point is that its core concept or important APIs have remained almost the same. I believe this is the proof that the original value of React is so significant that it will never decay.
Although the core concept of React has not changed much, the method of using it has gone through quite a change. In particular, with the birth of Redux in 2015, concepts like using a single immutable store, the division between components and containers, and asynchronous processing through Redux-Thunk/Redux-Saga have been established as common patterns.
And, yet another standardized and significant concept is the reusable code pattern, by using Higher Order Component, or HOC. As the existing mixin pattern began to be recognized as anti-patterns, and with the support of famous programmers who made React or Redux, HOC has emerged as a standard React method for code reuses. However, just like all the other patterns, HOC is not a cure-all for every case and may result in even worse codes than by mixins. In particular, for those who are new to React, HOC comes as unfamiliar and awkward concept and they are highly likely to misuse it without knowing what HOC is really about.
I have created a complicated application which is quite large in size based on React for more than two years and have accumulated knowhow to share. It is still not enough, but I’d like to delve into HOC for the record, but also to share experience. In this article, let me start with what HOC is all about and go further into details regarding usage points and differences with other patterns.
Functional Programming
Developers often forget that React purports to become a functional programming, which is indeed one of the most important concepts. As it is not clearly defined on its official website, some people may oppose the idea. However, we can find the trace of it in many parts of React, and even Redux, which is the most popular store management library, also emphasizes pure function and immutable state, and recommends that a programming style similar to functional programming be used.
React was developed by Jordan Walke who is known to have been largely influenced by functional languages based in ML (Meta Language). He also developed Reason which was released recently on Facebook, and it is also based on Ocaml, one of ML, and developed into a familiar format of Javascript. In fact, developing Reason predates React, which is a byproduct of the process. In other words, React is a product of Javascript library in the process of front-end development with functional language.
It won’t be easy to pinpoint which part is similar to functional programming, but there is one definite feature to notice to explain HOC: that is, components are (pure) functions, and it is the most differentiating concept from other libraries, like Angular or Vue.
Components Are (Pure) Functions
React components are basically functions: that is pure functions. To be more specific, the input comes with props
and the ReactElement
tree is returned. React, in reality, can create components with pure functions and its official homepage also emphasizes the following:
All React components must act like pure functions with respect to their props.
Then, you might ask, “What is the class component which inherits React.Component
?”. Of course, managing a component’s state or implementing a particular function by hooks to a lifecycle at a particular point of time won’t be easy only with pure functions. Indeed, when React was first introduced, the API was designed to create components by using the createClass
function.
However, to be more specific, state of a component can be managed by closures of a function (not a pure function, in such case), and lifecycle-related functions may be bound by maps or properties of a component function. That is, application can be created while the concept remains that a component is a function. Still, for object-oriented developers, functional programming is not familiar and hence, they are provided with APIs of more familiar classes. For class components, as well, if the render method itself is considered as a component, then it is still valid to say that components are functions.
It is true that React components act differently from object-oriented components: messages are not exchanged by direct references between objects or method calls, and data flow is unidirectional only from parent (caller function) to child (callee function). In other words, to compose a complicated ReactElement
tree, call another component within a component, like a function, to receive the result (ReactElement
) and then only combine and return.
In fact, criticism on mixins does not point to React only. Problems for code reuse through mixins have already existed in object-oriented methods, even before React. It is not that no solutions are available with object orientation: however, for functional-oriented libraries to solve its own along with those of object-oriented methods will be waste of resources. Just like everyone has his own style, the world of functional programming must have its own solutions.
Higher Order Function
Anyone who is familiar to functional programming must easily be able to guess what the word HOC refers to, because there is a very similar concept in the world of functional programming, called Higher Order Function, or HOF. As the word suggests, HOC comes from HOF, so, before we get to know HOC, it will be helpful to learn about HOF.
HOF is a function that receives a function as a parameter and returns a new function. It might seem awkward to find three ‘functions’ in a single sentence, but its representation in codes as below might look easier to understand:
const fy = HOF(fx);
So, the function receives fx
as a parameter and returns fy
. This may be a difficult pattern to understand in Java or C# where function is not a first-class object, but it is quite common in a language like Javascript where function is a first-class object. Functions such as _.throttle
, _.debounce
, _.partial
, _.flip
, _.once
, which are commonly used in functional libraries, like Lodash, are all part of HOF. For instance, the _.partial
function returns a new function which has fixed parameter of an existing function.
const add = (v1, v2) => v1 + v2;const add3 = _.partial(add, 3);
const add5 = _.partial(add, 5);console.log(add3(10)); // 13
console.log(add5(10)); // 15
One of the advantages of HOF is that adding more functions to a function is reusable. For instance, to create add3
and add5
functions, with add
but without the partial function, the following two functions must be created:
const add3 = v => add(v + 3);
const add5 = v => add(v + 5);
However, with HOF, reusing the code which creates a new function by function is available. The above example is too simple to understand, but for more complex HOF, redundant codes of large volume could be removed.
Let’s implement the _.partial
function in the above so as to understand better. With the first parameter of the two fixed in the function, the implementation can be simplified as below. We used the arrow function as it is more intuitive for a simple implementation.
const partial = (f, v1) => v2 => f(v1, v2);const add3 = partial(add, 3);
const add5 = partial(add, 5);console.log(add3(10)); // 13
console.log(add5(10)); // 15
Obviously, the operation works the same as the example of _.partial
in the above.
Before we move on, let’s make a more useful HOF. As was mentioned, using a closure of a function can make a function with an internal state. For example, let’s make HOF which is designed to accumulate return values of a function from 0 and onwards and return applied values. From now on, let me use the function
keyword, for easier understanding, especially if you’re not familiar with arrow functions in complicated implementation.
function acc(f) {
let v = 0; return function() {
v = f(v);
return v;
}
}const acc3 = acc(add3);console.log(acc3()); // 3
console.log(acc3()); // 6
console.log(acc3()); // 9
You can make side effects by using HOF. For example, let’s create HOF designed for a log output to the console.log
before a result is returned. For a simplified implementation, only the functions receiving one parameter are required:
function logger(f) {
return function(v) {
const result = f(v);
console.log(result); return result;
}
}const add3Log = logger(add3);console.log(add3Log(10)); // 13, 13
console.log(add3Log(15)); // 18, 18
The result shows that logs are redundantly displayed. That is, logs remain internally before the result is properly returned.
By now, we have created three HOFs. Now it’s time to compose these HOFs to create a new function. Using all of partial
, acc
, and logger
can result in a function which accumulates results that adds by 3 starting from 0.
const acc3Log = logger(acc(partial(add, 3)));acc3Log(); // 3
acc3Log(); // 6
acc3Log(); // 9
Functional programming, by default, creates a commonly-available function in a small unit, leading to a program with the combination of them. HOF plays a very important role by providing a pattern to compose functions at the functional level so as to make them reusable.
Higher Order Component
As was briefly mentioned, HOC is named after HOF: that is, it refers to a function that receives a component as parameter and returns the component. To put it similarly to the definition of HOF earlier said, it is:
const compY = HOC(compX);
The truth is that the name HOC has faults: while HOF is a function that receives a function as parameter and then returns the function (note the three ‘functions’ in a sentence), HOC must be a component that receives a component as parameter and then returns the component. Nevertheless, HOC, in fact, also refers to a function, not a component. That’s why critics raise their voices: but at the same time, the fact it’s similar to HOF is more emphasized. So, why don’t we show more generosity?
Then, what are the usages of HOC? Now that we started off from HOF, let’s apply the example once again to the component. First, just like the partial
function, the component with fixed props can be returned, as below:
function withProps(Comp, props) {
return function(ownProps) {
return <Comp {…props} {…ownProps} />
}
}
We can point out some differences from the partial
example, though. First, components receive props objects, not parameters, as the input value: that is, to fixate particular props, receive the props as objects and then combine them to the props of a returned component. Another difference lies in that components return ReactElement
, not a simple value. That is, to be returned, the passed component must be converted to ReactElement
by using JSX.
Now, let’s look at the example as below that uses this withProps
HOC.
function Hello(props) {
return <div>Hello, {props.name}. I am {props.myName}</div>
}const HelloJohn = withProps(Hello, {name: ‘John’});
const HelloMary = withProps(Hello, {name: ‘Mary’});const App = () => (
<div>
<HelloJohn myName=”Kim” />
<HelloMary myName=”Lee” />
</div>
)
The Hello
component is required to enter name
and myName
as props. Once components named HelloJohn
and HelloMary
, which have fixed names by using withProps
, are created, they can make use of the fixed name and can be used only with a myName
.
For another trial, let’s implement logger
in the above to fit for HOC. As components do not require logging results, let me simply show the passed props on the console. Just like creating withProps
, keep note of the props and return values only.
function logger(Comp) {
return function(props) {
console.log(props);
return <Comp {…props} />
}
}
Like HOF, HOC can be created into a component, composed of many HOCs. Now, let’s compose the two HOC in the above:
const HelloJohn = withProps(logger(Hello), {name: ‘John’});
const HelloMary = withProps(logger(Hello), {name: ‘Mary’});
Now, HelloJohn
and HelloMary
will show their passed props onto the console at every rendering. You can find through the rendering of the App component that each name
and myName
props of a component are shown on the console.
What Are Available with HOC
So far, we have written simple HOCs to learn the concept, but you may feel it not so useful to have only simple HOCs. However, there are much more and variable things you can do with HOC, in addition to almost all that were available by mixins. The most commonly-used HOC type is connecting stores with components. One of the best examples is the connect
function of React-Redux: specifically, this is the helper function which creates HOC. The connect
function receives mapStateToProps
, which converts the store state into props, and mapDispatchToProps
, which connects the action-creating function with the store dispatch, as parameters, so as to return a new HOC.
import {connect} from 'react-redux';
import {PersonComponent} from './person';const mapStateToProps = state => ({
name: state.name,
age: state.age
});const mapDispatchToProps = {
setName: (name) => {type: 'SET_NAME', name},
setAge: (age) => {type: 'SET_AGE', age}
};// Create a HOC
const connectHOC = connect(mapStateToProps, mapDispatchToProps);// Create a component with the HOC applied
const ConnectedComponent = connectHOC(PersonComponent);
More important functions are available with HOC as follows:
- Inject lifecycle methods as props
- Inject states and event handlers as props
- Manipulate props
- Extend the render method
To inject lifecycle methods or states into a component, you should use class components instead of function components. I will cover this in the following chapter with more practical examples. If you want to implement the features listed above, I recommend checking Recompose. Recompose is a React utility belt for function components and HOCs, which we can think of as Lodash for React. If you use functions such as withState or lifecycle provided by Recompose, you can easily inject lifecycle methods or states into a component using just function components. As it also provides other useful helpers for HOC, taking a look at the index of the API page will be very helpful to understand how HOCs can be used in various use cases.
Conclusion
In chapter 1, we’ve looked through the basic concept of HOC along with simple usage of it. Unlike the general description by which how mixin codes are changed to HOC, this article starts with the concept of functional programming to show HOF and move on to HOC. It is based on my personal belief that keeping in mind that React aims for functional programming helps to write codes more naturally.
In the next chapter, I’d like to touch upon practical HOC patterns with notes to take, by creating more useful HOC mocks.
Originally posted at Toast Meetup written by DongWoo Kim.