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.
Continue reading Higher-order Components: A React Application Design Pattern on SitePoint.
No comments:
Post a Comment