In my work-life I often get the opportunity to teach and share skills with more junior developers regarding development with React and frontend development in general. I decided it was time to put these small sessions into a more compact and sharable format and hence here I am!
Despite the title of this article Higher Order Components (or simply HOCs) aren't really a dummy concept and can be quite tricky for people to wrap their head around.
So a word of caution, this is a "for dummies" but it does assume some React knowledge and ES6!
Now with that out of the way; let me give you a little background. It might not be super obvious how this relates to the topic but bare with me. It should all make sense soon enough!
Currying
Despite the somewhat culinary sounding tone of this word it's actually very much a serious and useful mathematical concept best described by wikipedia:
In mathematics and computer science, currying is the technique of translating the evaluation of a function that takes multiple arguments into evaluating a ...
Just kidding, it's obviously best understood with code
, so here you go! :)
const multiply = x => y => x * y;
So we now have a function multiply
that takes an argument x
and replies a new function which takes an argument y
and returns x * y
. Or order to invoke this function we could type:
multiply(2)(2);
// Or using a console.log
console.log(multiply(2)(3));
// Output: 6
We said argument x
is 2
and argument y
is 3
which mean that what we'll get returned is 2 * 6
- if that wasn't already obvious to you!
const fetch = require('node-fetch');
let api = x => y => fetch(`https://jsonplaceholder.typicode.com/${x}/${y}`);
You can run this by initialising an npm project and installing node-fetch
. There's plenty of tutorials on how to do this, and to save time I'll presume that you already know it but in case you need a reminded it's npm init
and npm i -S node-fetch
:)
In this example we have a RESTful API that we're communicating with. Now what we could do is this:
const fetch = require('node-fetch');
let api = x => y => fetch(`https://jsonplaceholder.typicode.com/${x}/${y}`);
const albums = api('albums');
const firstAlbum = albums(1);
firstAlbum
.then(res => res.json())
.then(console.log);
// { userId: 1, id: 1, title: 'quidem molestiae enim' }
And suddenly currying is starting to look a bit more powerful and useful. Now with this technique we can create very reusable code.
Oh, I never mentioned it. But now since I got you hooked: curry comes from the logician Haskell Curry. You'll find more Haskell (i.e. the language named after the very same logician) tutorials on my page soon :)
const fetch = require('node-fetch');
let api = x => y => fetch(`https://jsonplaceholder.typicode.com/${x}/${y}`);
const albums = api('albums');
[1, 2, 3, 4, 5].map(n =>
albums(n)
.then(res => res.json())
.then(res => console.log(res))
);
This will fetch all albums starting from id 1 to id 5. Now if you're anything like me you're now bursting with ideas on how to implement this into your latest code base. That's all good my dude - go ahead! But remember, this was a HOC tutorial and what does currying have to do with HOCs?
The Higher Order Component
import React from 'react';
const withSecret = Component => class extends React.Component {
state = {
secret: 'Very53cr37Token'
}
render() {
return (
<Component secret={this.state.secret} />
)
}
}
const App = ({ secret }) => (
<div>{secret}</div>
);
export default withSecret(App);
Now you can run this code by going to this repository and cloning it, this piece of code is located in src/App.js
. Now to begin with, what's happening here is that we're exporting withSecret
.
It takes one argument and immediately returns an "anonymous class" (basically meaning we haven't given it a specific name) extending React.Component
. Notice how in the render
method we're returning some JSX. But what we're returning is the argument from above.
And yes, of course I know that this secret isn't very secret at all. This is no way to store actual secrets. But as far as scoping goes this variable is inaccessible (hence, secret) from other components.
const withSecret = Component => ...
render() {
return (
<Component secret={this.state.secret} />
)
}
So we're assuming (and it won't run unless it is as far as we're concerned) that the Component
argument is a component of some sort i.e. a (React) component, functional component or a PureComponent. But we're also giving this component a new prop called secret
which is also being rendered in our App
component.
const App = ({ secret }) => (
<div>{secret}</div>
);
So by wrapping our App component in withSecret
we're giving it access to the prop secret
which is now being rendered. Now we're not limited to rendering out strings like this. We can also give components access to functionality by wrapping them in a HOC.
Adding functionality
import React from 'react';
const withSecret = Component => class extends React.Component {
state = {
secret: 'Very53cr37Token'
}
setSecret = e => this.setState({
secret: e.target.value
})
render() {
return (
<Component secret={this.state.secret} setSecret={this.setSecret} />
)
}
}
const App = ({ secret, setSecret }) => (
<div>
{secret}
<input type="text" onChange={setSecret} />
</div>
);
export default withSecret(App);
You can find this code checking out the adding-functionality
branch of the repository (like this git checkout adding-functionality
).
This time we added a method to our HOC.
setSecret = e => this.setState({
secret: e.target.value
})
You should be fairly familiar with what this does. But it takes the value of some HTML element that emits a event (in our case a <input />
and sets the state property secret
to whatever value it receives. The value is being set down in our App component on line 21.
<input type="text" onChange={setSecret} />
The method setSecret
is now exposed to our App because it's being inherited by our HOC on line 12.
<Component secret={this.state.secret} setSecret={this.setSecret} />
You can run the script and type something into the input field that shows up. Voila, you've successfully updated the secret. But not only that, you now have a fully reusable piece of code that you can wrap around any other component we're you wish to add functionality. Of course you could also add styling or for example, a navigation bar (navbar), and wrap all component that needs a navbar with your withNavbar
HOC etc.
The possibilities are endless. If you're familiar with Redux, then you might have heard that the connect
function is a HOC? Well, now you can start to understand why that is and how that works!
Now, if we think about it. I'm sure you've used a curried function before. Do you remember ever using map
or filter
? Because what are they? They are functions that takes functions as an argument and applies that function to every element in a list/array.
Happy hacking!
Top comments (0)