albertofortes

UI engineer | front-end React developer

JavaScript, ReactJS, Interview Question series, React intermediare topics,

Higher-Order Components

2021-04-25

Higher-Order Components

React interview cheatsheet series

A Higher-Order Components is a technique to reuse components, it's just a pattern to do a normal component but specificaly made to reuse code. In summary is just a function that takes a component and returns a new component which que want to reuse across the app.

If you sometimes had to copy&paste code from components to other, you found a perfect use case of HOCs. Let's se a very practical example of Higher-Order component:

Imagine we have a blog with comments on every post. There is a comment box following of a list of the comments. We want that the comment list is visible for all the visitors of the site, but the ability to comment, edit, or remove the own comments requires user authentification.

So in that case, we need to component that ask for the user autentification, so this is a perfect case to creame a Higher-Order Component that we could call RequireAuthentification.

If the RequireAuthentification component returns false, won't be possible to access to CommentBox controller. This HOC will act sort of a middleware of the final component, so:

1APP -> CommentList 2APP -> RequireAuthentification (HOC) -> CommentBox 3App -> RequireAuthentification (HOC) -> Edit screen

Steps to create an Higher-Order component.

We can follow some steps in order to create a HOOC:

  1. Write the logic we wan to use in a regular component.
  2. Create a HOC file and adding the HOC scaffold (in other words, the HOC bolierplate).
  3. Move the reusable logic into the HOC created at point 2.
  4. Pass props, config, or behavior through to child component.

As we said, an HOC is a function that takes a component as an argument and returns a component. So this Higher-Order component always will have a patter similar to this:

1import React from 'react'; 2 3const HigherOrderComponent = (WrappedComponent) => { 4 class HOC extends React.Component { 5 render() { 6 return <WrappedComponent />; 7 } 8 } 9 10 return HOC; 11}; 12 13// Invoke the HOC like: 14const SimpleHOC = HigherOrderComponent(MyComponent);

So in our RequireAuthentification example we should invoke in this way:

1RequireAuthentification(CommentBox);

Let's go to create our HOC in these steps.

Step 1: Write the logic we wan to use in a regular component

We have a dummy APP which contains two components: CommentBox and the propper App:

1const { useState } = React; 2 3function CommentBox() { 4 const [comment, setComment] = useState(''); 5 6 const handleChange = (event) => { 7 setComment(event.target.value); 8 }; 9 10 const handleSubmit = (event) => { 11 event.preventDefault(); 12 alert(comment); 13 setComment(''); // clear the textarea 14 }; 15 16 return ( 17 <div> 18 <form onSubmit ={handleSubmit}> 19 <h4>Add a Comment</h4> 20 <textarea onChange={handleChange} value={comment} /> 21 <div> 22 <button className="submit-comment">Submit Comment</button> 23 </div> 24 </form> 25 </div> 26 ); 27} 28 29function App(props) { 30 const [auth, setAuth] = useState(false); 31 32 const renderButton = () => { 33 if(auth) { 34 return <button onClick={() =>setAuth(false)}>Sign Out</button>; // calling the action 35 } else { 36 return <button onClick={() => setAuth(true)}>Sign In</button> 37 } 38 }; 39 40 return ( 41 <div> 42 {renderButton()} 43 <CommentBox /> 44 </div> 45 ); 46} 47 48function mapStateToProps(state) { 49 return { auth: state.auth } 50} 51 52ReactDOM.render( 53 <App />, 54 document.getElementById('root') 55);

When the user is not logged, the button shows "log in", and viceversa. Now the code just show that button at App.renderButton() and the comment box bellow. If the user clicks on Log in, the state auth becomes true and viceversa (obviously no more logic is needed for this dummy example).

Let's go with next step.

Step 2: Create a HOC file and adding the HOC boilerplate
1const RequireAuth = OriginalComponent => { 2 class NewComponent extends React.Component { 3 render() { 4 return <OriginalComponent {...this.props} /> 5 } 6 } 7 8 return NewComponent; 9}
Step 3: Move / Create the reusable logic into the HOC created at point 2.
1const RequireAuth = OriginalComponent => { 2 class NewComponent extends React.Component { 3 render() { 4 if (!this.props.auth) { 5 return false; 6 } else { 7 return <OriginalComponent {...this.props} /> 8 } 9 } 10 } 11 12 return NewComponent; 13}
Step 4: Pass props, config, or behavior through to child component.

To call a HOC we use this syntax:

So instead of calling to <CommentBox /> at App Controller render method, we create a const like:

1const CommentBoxWithAuth = RequireAuth(CommentBox);

So the HOC wraps the original CommentBox component and it will return the result that is another Component...

And the final code would be:

1const { Component, useState, useEffect } = React; 2 3const RequireAuth = OriginalComponent => { 4 class NewComponent extends React.Component { 5 render() { 6 if (!this.props.auth) { 7 return false; 8 } else { 9 return <OriginalComponent {...this.props} /> 10 } 11 } 12 } 13 14 return NewComponent; 15} 16 17const CommentBox = (props) => { 18 const [comment, setComment] = useState(''); 19 20 const handleChange = (event) => { 21 setComment(event.target.value); 22 }; 23 24 const handleSubmit = (event) => { 25 event.preventDefault(); 26 alert(comment); 27 setComment(''); // clear the textarea 28 }; 29 30 return ( 31 <div> 32 <form onSubmit ={handleSubmit}> 33 <h4>Add a Comment</h4> 34 <textarea onChange={handleChange} value={comment} /> 35 <div> 36 <button className="submit-comment">Submit Comment</button> 37 </div> 38 </form> 39 </div> 40 ); 41} 42 43function App(props) { 44 const [auth, setAuth] = useState(false); 45 46 const renderButton = () => { 47 if(auth) { 48 return <button onClick={() =>setAuth(false)}>Sign Out</button>; // calling the action 49 } else { 50 return <button onClick={() => setAuth(true)}>Sign In</button> 51 } 52 }; 53 54 return ( 55 <div> 56 {renderButton()} 57 <CommentBoxWithAuth auth={auth} /> 58 </div> 59 ); 60} 61 62const CommentBoxWithAuth = RequireAuth(CommentBox); 63 64ReactDOM.render( 65 <App />, 66 document.getElementById('root') 67);

See the code in codepen

Useful links: