In this article, we’ll discuss how to use higher-order components to keep your React applications tidy, well-structured and easy to maintain. We’ll discuss how pure functions keep code clean and how these same principles can be applied to React components.
Pure Functions
A function is considered pure if it adheres to the following properties:
- all the data it deals with is declared as arguments
- it doesn’t mutate data it was given or any other data (these are often referred to as side effects)
- given the same input, it will always return the same output.
For example, the add
function below is pure:
function add(x, y) {
return x + y;
}
However, the function badAdd
below is impure:
let y = 2;
function badAdd(x) {
return x + y;
}
This function is not pure because it references data that it hasn’t directly been given. As a result, it’s possible to call this function with the same input and get different output:
let y = 2;
badAdd(3) // 5
y = 3;
badAdd(3) // 6
To read more about pure functions you can read “An introduction to reasonably pure programming” by Mark Brown.
Whilst pure functions are very useful, and make debugging and testing an application much easier, occasionally you’ll need to create impure functions that have side effects, or modify the behavior of an existing function that you’re unable to access directly (a function from a library, for example). To enable this, we need to look at higher-order functions.
Higher-order Functions
A higher-order function is a function that returns another function when it’s called. Often they also take a function as an argument, but this isn’t required for a function to be considered higher-order.
Let’s say we have our add
function from above, and we want to write some code so that when we call it, we log the result to the console before returning the result. We’re unable to edit the add
function, so instead we can create a new function:
function addAndLog(x, y) {
const result = add(x, y);
console.log(`Result: ${result}`);
return result;
}
We decide that logging results of functions is useful, and now we want to do the same with a subtract
function. Rather than duplicate the above, we could write a higher-order function that can take a function and return a new function that calls the given function and logs the result before then returning it:
function logAndReturn(func) {
return function(...args) {
const result = func(...args)
console.log('Result', result);
return result;
}
}
Now we can take this function and use it to add logging to add
and subtract
:
const addAndLog = logAndReturn(add);
addAndLog(4, 4) // 8 is returned, ‘Result 8’ is logged
const subtractAndLog = logAndReturn(subtract);
subtractAndLog(4, 3) // 1 is returned, ‘Result 1’ is logged;
logAndReturn
is a higher-order function because it takes a function as its argument and returns a new function that we can call. These are really useful for wrapping existing functions that you can’t change in behavior. For more information on this, check M. David Green’s article “Higher-Order Functions in JavaScript”, which goes into much more detail on the subject.
See the Pen
Higher Order Functions by SitePoint (@SitePoint)
on CodePen.
Higher-order Components
Moving into React land, we can use the same logic as above to take existing React components and give them some extra behaviors.
Note: with the introduction of React Hooks, released in React 16.8, higher-order functions became slightly less useful because hooks enabled behavior sharing without the need for extra components. That said, they are still a useful tool to have in your belt.
In this section, we’re going to use React Router, the de facto routing solution for React. If you’d like to get started with the library, I highly recommend the React Router documentation as the best place to get started.
React Router’s Link component
React Router provides a <NavLink>
component that’s used to link between pages in a React application. One of the properties that this <NavLink>
component takes is activeClassName
. When a <NavLink>
has this property and it’s currently active (the user is on a URL that the link points to), the component will be given this class, enabling the developer to style it.
This is a really useful feature, and in our hypothetical application we decide that we always want to use this property. However, after doing so we quickly discover that this is making all our <NavLink>
components very verbose:
<NavLink to="/" activeClassName="active-link">Home</NavLink>
<NavLink to="/about" activeClassName="active-link">About</NavLink>
<NavLink to="/contact" activeClassName="active-link">Contact</NavLink>
Notice that we’re having to repeat the class name property every time. Not only does this make our components verbose, it also means that if we decide to change the class name we’ve got to do it in a lot of places.
Instead, we can write a component that wraps the <NavLink>
component:
const AppLink = (props) => {
return (
<NavLink to={props.to} activeClassName="active-link">
{props.children}
</NavLink>
);
};
And now we can use this component, which tidies up our links:
<AppLink to="/home" exact>Home</AppLink>
<AppLink to="/about">About</AppLink>
<AppLink to="/contact">Contact</AppLink>
In the React ecosystem, these components are known as higher-order components, because they take an existing component and manipulate it slightly without changing the existing component. You can also think of these as wrapper components, but you’ll find them commonly referred to as higher-order components in React-based content.
Better Higher-order Components
The above component works, but we can do much better. The <AppLink>
component that we created isn’t quite fit for purpose.
Accepting multiple properties
The <AppLink>
component expects two properties:
this.props.to
, which is the URL the link should take the user tothis.props.children
, which is the text shown to the user
However, the <NavLink>
component accepts many more properties, and there might be a time when you want to pass extra properties along with the two above, which we nearly always want to pass. We haven’t made <AppLink>
very extensible by hard coding the exact properties we need.
The JSX spread
JSX, the HTML-like syntax we use to define React elements, supports the spread operator for passing an object to a component as properties. For example, the code samples below achieve the same thing:
const props = { a: 1, b: 2};
<Foo a={props.a} b={props.b} />
<Foo {...props} />
Using {...props}
spreads each key in the object and passes it to Foo
as an individual property.
We can make use of this trick with <AppLink>
so we support any arbitrary property that <NavLink>
supports. By doing this we also future proof ourselves; if <NavLink>
adds any new properties in the future our wrapper component will already support them.
const AppLink = (props) => {
return <NavLink {...props} activeClassName="active-link" />;
}
Now <AppLink>
will accept any properties and pass them through. Note that we also can use the self-closing form instead of explicitly referencing {props.children}
in between the <NavLink>
tags. React allows children
to be passed as a regular prop or as child elements of a component between the opening and closing tag.
Property ordering in React
Imagine that for one specific link on your page, you have to use a different activeClassName
. You try passing it into <AppLink>
, since we pass all properties through:
<AppLink to="/special-link" activeClassName="special-active">Special Secret Link</AppLink>
However, this doesn’t work. The reason is because of the ordering of properties when we render the <NavLink>
component:
return <Link {...props} activeClassName="active-link" />;
When you have the same property multiple times in a React component, the last declaration wins. This means that our last activeClassName="active-link"
declaration will always win, since it’s placed after {...this.props}
. To fix this, we can reorder the properties so we spread this.props
last. This means that we set sensible defaults that we’d like to use, but the user can override them if they really need to:
return <Link activeClassName="active-link" {...props} />;
By creating higher-order components that wrap existing ones but with additional behavior, we keep our code base clean and defend against future changes by not repeating properties and keeping their values in just one place.
Higher-order Component Creators
Often you’ll have a number of components that you’ll need to wrap in the same behavior. This is very similar to earlier in this article when we wrapped add
and subtract
to add logging to them.
Let’s imagine in your application you have an object that contains information on the current user who is authenticated on the system. You need some of your React components to be able to access this information, but rather than blindly making it accessible for every component you want to be more strict about which components receive the information.
The way to solve this is to create a function that we can call with a React component. The function will then return a new React component that will render the given component but with an extra property which will give it access to the user information.
That sounds pretty complicated, but it’s made more straightforward with some code:
function wrapWithUser(Component) {
// information that we don’t want everything to access
const secretUserInfo = {
name: 'Jack Franklin',
favouriteColour: 'blue'
};
// return a newly generated React component
// using a functional, stateless component
return function(props) {
// pass in the user variable as a property, along with
// all the other props that we might be given
return <Component user={secretUserInfo} {...props} />
}
}
The function takes a React component (which is easy to spot given React components commonly have capital letters at the beginning) and returns a new function that will render the component it was given with an extra property of user
, which is set to the secretUserInfo
.
Now let’s take a component, <AppHeader>
, which wants access to this information so it can display the logged-in user:
const AppHeader = function(props) {
if (props.user) {
return <p>Logged in as {props.user.name}</p>;
} else {
return <p>You need to login</p>;
}
}
The final step is to connect this component up so it’s given this.props.user
. We can create a new component by passing this one into our wrapWithUser
function:
const ConnectedAppHeader = wrapWithUser(AppHeader);
We now have a <ConnectedAppHeader>
component that can be rendered, and will have access to the user
object.
I chose to call the component ConnectedAppHeader
because I think of it as being connected with some extra piece of data that not every component is given access to.
This pattern is very common in React libraries, particularly in Redux, so being aware of how it works and the reasons it’s being used will help you as your application grows and you rely on other third-party libraries that use this approach.
Conclusion
This article has shown how, by applying principles of functional programming such as pure functions and higher-order components to React, you can create a codebase that’s easier to maintain and work with on a daily basis.
By creating higher-order components, you’re able to keep data defined in only one place, making refactoring easier. Higher-order function creators enable you to keep most data private and only expose pieces of data to the components that really need it. By doing this you make it obvious which components are using which bits of data, and as your application grows you’ll find this beneficial.
If you have any questions, I’d love to hear them. Feel free to ping me @Jack_Franklin on Twitter.