In this tutorial, we will approach React.js Higher-Order Components (HOCs), with the help of an easy to understand, basic example. To follow along, you will need some React.js and JavaScript knowledge, but everything is detailed in the checklist below.
Download the source code
You can follow along, and build your app from the ground up, or better yet, you can download the files used in the tutorial from here.
BUILD GAMES
FINAL DAYS: Unlock 250+ coding courses, guided learning paths, help from expert mentors, and more.
Before moving forward
Note that after downloading the files, to get the app running you will have to navigate to the project folder: cd my-hoc and install the dependencies by running npm install in your terminal.
What will you need – checklist
- Basic knowledge of React.js and JavaScript (ES6 Arrow Functions). You can check out the Beginner’s Guide to React.js with examples tutorial, or the Build Web Apps with React JS and Flux course, which covers everything you need, and more. If you have a basic knowledge of React.js and want to continue your learning journey by adding more complex patterns/concepts to your tool-belt, then this article is for you! We will use an easy example of HOCs to get you familiar with the possibilities, which you can later apply to your more advanced projects.
- Text editor of your choice. Some popular text editors are Atom, Sublime Text, Notepad++. Syntax highlighting is a plus.
- To get started quickly, we will use create-react-app, which handles the creation of all our starter files, so we can focus on development faster. If you wish to follow along, make sure to have Node.js >= 6 on your local development machine and npm installed, as this is a requirement for the create-react-app .
What are Higher-Order Components (HOCs) ?
Basically, a Higher-Order Component (HOC) is a function that takes a component as an argument, and returns another component. The returned component is not any kind of component, it’s a component with superpowers (sadly not the kind of superpowers we all wish we had when we were kids), so to say.
It’s actually that simple! ????
Note from The React docs:
HOCs are not part of the React API, per se. They are a pattern that emerges from React’s compositional nature.
Higher-Order Components are a powerful pattern, for providing and fetching data to other components.
When can we use Higher-Order Components (HOCs) ?
Whenever we want to wrap a repeating pattern. Also, having repeating code with a lot of functionality would be difficult to maintain in larger applications.
However it’s best not to overuse them – and always check if another pattern could be more appropriate.
That being said, let’s get started!
We will begin by bootstrapping our project with create-react-app . Let’s open up our terminal window, and run:
npx create-react-app my-hoc
You can also use npm init , which is available in npm > 6
npm init react-app my-hoc
Or if you prefer Yarn
yarn create react-app my-hoc
This will create the my-hoc folder, with our initial project structure.
Once the installation is completed, still in our terminal, we will navigate to the project folder: cd my-hoc
And start the development server with npm start .
Note: If you are using Yarn, you can run yarn start .
A new browser page should open, but if it doesn’t, navigate to http://localhost:3000/ to see your app running.
Our initial project structure will look like this:
Now, let’s do some cleaning. We will remove the logo and the generated boilerplate code, and also replace the starter css with our own styles.
The files that we’ll modify are App.js and App.css , which after refactor, will look like this:
App.js file:
import React, { Component } from 'react' import './App.css' class App extends Component { render() { return ( <div className="App"> </div> ) } } export default App;
App.css file:
.App { display: flex; border: 1px solid #000; margin: 25px auto; padding: 25px; max-width: 750px; } .score-panel { display: flex; flex-direction: column; align-items: center; border: 10px solid lightblue; width: 70%; } .player-info { display: flex; flex-direction: column; border: 1px solid #000; border-radius: 15px; margin: 5px; padding-left: 15px; width: 30%; }
We won’t do more styling for this particular example, but feel free to unleash your inner artist and add an awesome look to the application as we build our components. ????
What will we build ?
Let’s say that we have a game, and that we want some of the player information, such as the player name and player score, to be displayed in our page.
We will want the name and score to be displayed in two places, in the score panel, and in the player card.
In future articles, we will look into how to build/use an API to store our player data, as well as building a game, but for now, for the simplicity of our example, we will hard-code the data in a separate file.
In our project src folder, we will create a new file, data.js , which will store and export our playerData .
data.js file:
const playerData = { name: 'Johnny Doey', score: 'over 9000' } export default playerData
While we’re here, in the src folder, let’s go ahead and create another folder, components , which will hold our PlayerInfo and ScorePanel components, that will get and display the player data. Let’s give them some code!
PlayerInfo.js file:
import React, { Component } from 'react' import playerData from '../data.js' export default class PlayerInfo extends Component { constructor(props) { super(props); this.state = { name: "Welcome back...", score: "Calculating..." } } componentDidMount() { this.setState({ name: playerData.name, score: playerData.score }) } render() { return( <div className="player-info"> <p>Name: { this.state.name }</p> <p>Score: { this.state.score }</p> </div> ) } }
ScorePanel.js file:
import React, { Component } from 'react' import playerData from '../data.js' export default class ScorePanel extends Component { constructor(props) { super(props); this.state = { name: "Welcome back...", score: "Calculating..." } } componentDidMount() { this.setState({ name: playerData.name, score: playerData.score }) } render() { return( <div className="score-panel"> <h2>{ this.state.name }</h2> <p>{ this.state.score }</p> </div> ) } }
In both components, we’ve imported the playerData from our data.js file.
We’ve defined the state with some initial values, and in the componentDidMount life-cycle method we’ve set the state to the playerData values.
The componentDidMount life-cycle method is available after the component has mounted, after the HTML from render has completed its loading. It is called once in the component life-cycle, and it signals that the component and all of its sub-components have rendered properly.
This is the place where we can do anything that we want to set up in the DOM, such as API calls.
Moving forward, let’s make use our newly created components, by including them in the App.js file.
import React, { Component } from 'react' import './App.css' import PlayerInfo from './components/PlayerInfo' import ScorePanel from './components/ScorePanel' class App extends Component { render() { return ( <div className="App"> <ScorePanel /> <PlayerInfo /> </div> ); } } export default App
The results displayed on the screen:
And our folder structure will be:
But wait a second…let’s take a second look at our PlayerInfo and ScorePanel components.
In both of them we are getting the playerData , setting their state to the desired values, and returning something that will be rendered.
Déjà vu ? Repeating patterns ? It’s HOC Time!
As mentioned at the beginning of this article, in React, a HOC is a function that takes a component as an argument, and returns another component with superpowers.
By doing so, we can break our application into simple and reusable functions.
To put this into practice…
We will create our HOC in a new file, withPlayerData.js . The naming convention is to use with as a prefix for the HOC, to describe the returning superpower.
This makes code maintenance much easier, as well as finding bugs faster . Also, it will help the reader to understand the code logic easily.
In our withPlayerData.js file:
import React, { Component } from 'react' import playerData from '../data.js' const withPlayerData = WrappedComponent => class extends Component { constructor(props) { super(props) this.state = { name: "Welcome back...", score: "Calculating..." } } componentDidMount() { this.setState({ name: playerData.name, score: playerData.score }) } render() { return( <WrappedComponent playerName={this.state.name} playerPoints={this.state.score} {...this.props} /> ) } } export default withPlayerData
Again, we are importing React and our playerData.
Next, we define our withPlayerData function, which takes WrappedComponent as an argument.
Then, we have the constructor with our state, which stores the name and score of our player, the componentDidMount life-cycle method, that we will use to get our player name and score from the data.js file, and last but not least, the render method. So basically we’ve moved the repeated logic from the other two components into this one.
In the render methods return statement, we have our initial argument – the WrappedComponent .
This WrappedComponent has our props:
playerName={this.state.name} playerPoints={this.state.score}
And it’s receiving the source components props from {…this.props} .
What does {…this.props} actually do.
It’s called spread attribute, and it makes the passing of props easier.
Imagine that later on, we will want to add more player data such as unlockables, skins, stats, skills, currency and so on. If you have a component that accepts a number of props, passing them down can be overwhelming and tedious when/if their number grows. Taking this into account, instead of passing props like this:
<OurComponent foo={} bar={} baz={} lorem={} ipsum={} kitty={} />
We can simply wrap them up in an object and use the spread notation to pass them to our component: <OurComponent {…props} /> , and in OurComponent , we can then use the props normally.
i.e
this.props.foo this.props.bar this.props.baz this.props.lorem this.props.ipsum this.props.kitty
Back to our withPlayerData HOC, in which we have now created a reusable provider for the player data!
Refactoring our application…
…to make use of the newly acquired superpower, Higher-Order Component.
In our ScorePanel and PlayerInfo files, we’ll first remove the playerData , and import our withPlayerData HOC, to make use of it.
ScorePanel.js file:
import React, { Component } from 'react' // import playerData from '../data.js' import withPlayerData from './withPlayerData'
Next, let’s remove the constructor , state , and componentDidMount life-cycle method.
Because we have moved all the functionality to our HOC, our component only needs to render, so we can use a functional component instead of a Class component.
A functional component is just a JavaScript function which accepts an argument(props), and returns a React element. They are useful for presentational components which focus on the UI, rather than on behaviour.
So why use a functional component?!
- Because it’s easier to read
- Because it’s easier test
- Because it’s less code
The resulting, refactored functional component:
const ScorePanel = ({playerName, playerPoints}) => ( <div className="score-panel"> <p className="user-name">{ playerName } <span>({ playerPoints })</span></p> </div> )
And to use our HOC, we just have to export it with our component passed in as an argument:
export default withPlayerData(ScorePanel)
Finally, our ScorePanel components new look:
import React from 'react' import withPlayerData from './withPlayerData' const ScorePanel = ({playerName, playerPoints}) => ( <div className="score-panel"> <h2>{ playerName }</h2> <p>{ playerPoints }</p> </div> ) export default withPlayerData(ScorePanel)
Nice, clean and simple.
Applying the same refactor in the PlayerInfo.js component:
import React from 'react' // import playerData from '../data.js' import withPlayerData from './withPlayerData' // export default class PlayerInfo extends React.Component { // constructor(props) { // super(props); // // this.state = { // name: "Welcome back...", // score: "Calculating..." // } // } // // componentDidMount() { // this.setState({ // name: playerData.name, // score: playerData.score // }) // } // // render() { // return( // <div className="player-info"> // <p>Name: { this.state.name }</p> // <p>Score: { this.state.score }</p> // </div> // ) // } // } const PlayerInfo = ({playerName, playerPoints}) => ( <div className="player-info"> <p>Name: { playerName }</p> <p>Score: { playerPoints }</p> </div> ) export default withPlayerData(PlayerInfo)
The results:
And that’s it! We have used a Higher-Order Component to handle our playerData logic and pass it to our other components. As our applications grow, it’s common to have components that display the same data, but in a different way. That being said, as you can see, Higher-Order Components are useful.
Some HOCs Cons
- Reusability – can be as much of a con, as it can be a pro. Placing a lot of reusable code into a Higher-Order Component, can involve passing a lot of props, thus leading to collisions.
- Refs aren’t passed through.
- Static methods must be copied over
Where to go from here
If you want to strengthen your understanding beyond this example (which is highly recommended), and don’t have any ideas on what to build next, try making a Higher-Order Component that fetches data from an API, or one that saves/loads a components state to/from local storage. Or how about one that wraps the passed in component in a container with an awesome style ?
Conclusion and Comments
Did you find the examples illustrated in this article too easy? Too complex? Just right? Have something in mind that you would like me to write about? Did you build a fun project using the concepts learned?
Let me know in the comments section below.
In the meantime, Happy Coding! ????