REACT PATTERN : HOC

Ravi Chandola
Javarevisited
Published in
5 min readMay 24, 2024

A Higher-Order Component (HOC) in React is a pattern used to share common functionality between components without repeating code. It is a function that takes a component and returns a new component, typically adding additional properties or behavior to the original component.

How HOCs are useful:

  1. Code Reusability: HOCs allow you to write reusable code. You can create a HOC that adds a particular functionality and use it across multiple components.
  2. Abstraction: They help in abstracting shared behavior, making the components cleaner and focused on their own responsibilities.
  3. Composition: HOCs can be composed together to enhance components with multiple behaviors.

When to use HOCs:

  • To share behavior across multiple components: When you notice that different components need to share the same logic (such as data fetching, input handling, or analytics integration), you can extract this logic into an HOC.
  • To modify props: You can use HOCs to modify the props before they reach a component, adding or changing properties as needed.
  • To manage state: An HOC can be used to encapsulate stateful logic, such as managing the state of a form or toggling visibility of a component.
  • To access context: If you need to access context in many components, an HOC can help to inject context as props.

When not to use HOCs:

  • When composition is sufficient: If a component can be enhanced by simply passing props, there’s no need to create an HOC.
  • When hooks are more appropriate: With the introduction of hooks in React 16.8, many use cases for HOCs can be more cleanly addressed using hooks, such as useState, useEffect, and useContext.
  • For one-off enhancements: If only a single component requires the added functionality, it may be better to enhance that component directly rather than creating an HOC.

Designing a Higher-Order Component:

Step 1: Create a Loading Component

First, we’ll need a basic loading component to display while data is being fetched.

// Loading.js
import React from 'react';

const Loading = () => <div>Loading...</div>;

export default Loading;

Step 2: Create the HOC

Next, we’ll create the HOC itself. This component will take a WrappedComponent and return a new component that conditionally renders either the WrappedComponent or the Loading component based on the isLoading prop.

import React from 'react';
import Loading from './Loading';

// This is the HOC function. It takes a component...
const withLoading = (WrappedComponent) => {
// ...and returns another component...
return class extends React.Component {
render() {
// ... that renders the Loading component if isLoading prop is true,
// otherwise it renders the WrappedComponent.
const { isLoading, ...otherProps } = this.props;
if (isLoading) {
return <Loading />;
}
return <WrappedComponent {...otherProps} />;
}
};
};

export default withLoading;

Step 3: Use the HOC

Now, let’s say we have a UserList component that fetches and displays a list of users. We'll use our withLoading HOC to enhance this component.

// UserList.js
import React, { Component } from 'react';
import withLoading from './withLoading';

class UserList extends Component {
render() {
const { users } = this.props;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
}

// We wrap our component with the withLoading HOC.
export default withLoading(UserList);

Step 4: Using the Enhanced Component

Finally, we’ll use our enhanced UserList component within a parent component, where we manage the fetching of users and the isLoading state.

// App.js
import React, { Component } from 'react';
import UserListWithLoading from './UserList';
// This is the enhanced component

class App extends Component {
state = {
isLoading: false,
users: [],
};

componentDidMount() {
this.fetchUsers();
}

fetchUsers = async () => {
this.setState({ isLoading: true });
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const users = await response.json();
this.setState({ users, isLoading: false });
} catch (error) {
console.error(error);
this.setState({ isLoading: false });
}
};

render() {
const { isLoading, users } = this.state;
return (
<div>
<h1>Users</h1>
{/* We pass the isLoading state and users to the enhanced component */}
<UserListWithLoading isLoading={isLoading} users={users} />
</div>
);
}
}

export default App;

Here, App is a component that fetches user data and stores it in its state along with an isLoading flag. It passes both to UserListWithLoading, which is the UserList component wrapped with the withLoading HOC. When isLoading is true, the Loading component is displayed. Once the data is fetched, isLoading becomes false, and the UserList component is rendered with the fetched data.

Explanation:

  • withLoading is an HOC that enhances any component with a loading state. It takes a component as its argument and returns a new component that can render a loading indicator.
  • The UserList component is a simple component that expects an array of users and renders them. It does not concern itself with the loading state.
  • withLoading(UserList) enhances UserList by wrapping it with additional functionality to handle the loading state.
  • In App, the state is managed to keep track of the loading status and user data. This state is passed down to the UserListWithLoading component.

This pattern is powerful because it separates the concern of fetching data and managing loading state from the presentation of the user data. This separation of concerns makes the code easier to maintain and reason about. However, with the introduction of React hooks, you might achieve the same functionality using useState and useEffect hooks in functional components, which could simplify the pattern even further.

Best Practices and Considerations:

  1. Pass Unrelated Props Through to the Wrapped Component: Make sure to pass through props that are meant for the wrapped component, so it receives the props intended for it.
  2. Maximize Composability: Write HOCs in a way that they can be composed together to enhance a component with multiple behaviors.
  3. Avoid State Duplication: Be careful not to duplicate the state between the HOC and the wrapped component.
  4. Convention: By convention, HOCs are prefixed with ‘with’, as in withRouter, withRedux, withStyles, etc.
  5. Static Methods: Be aware that HOCs do not automatically copy static methods of the wrapped component. You may need to copy them over explicitly if needed.
  6. Refs: HOCs can make it tricky to use refs because the ref will point to the outermost container component, not the wrapped component. To get around this, you can use React.forwardRef in React 16.3 and above.

--

--

Ravi Chandola
Javarevisited

As a technology enthusiast, I have a passion for learning and sharing my knowledge with others. https://www.linkedin.com/in/ravi-chandola-304522133