Master React.js design patterns with real-world examples — HOCs Part: 1

Mustafa Albaghdadi
7 min readApr 5, 2023

--

React.js HOCs
React.js Higher Order Components

React.js is the superstar of the JavaScript frontend development! It’s the go-to library for building snazzy user interfaces, and developers and organizations worldwide can’t get enough of it. With its groovy component-based architecture and awesome declarative rendering approach, React.js provides a powerful and flexible platform for constructing complex and scalable web applications.

But hey, let’s be real, as applications become bigger and more complex, keeping the codebase under control can be a bit of a headache. That’s where design patterns come in, like the superheroes they are! These are proven solutions to common problems that developers face during the software development process. Think of them as a set of best practices and guidelines that help you write cleaner, more modular, and more maintainable code.

There are loads of design patterns to choose from, but some of the most popular ones for React.js include:

  • Higher-Order Component (HOC) Pattern
  • Render Props Pattern
  • Redux and Context Patterns
  • Hooks Pattern
  • Error Boundaries
  • Container/Presenter Pattern
  • Compound Components Pattern.

Through this series of articles, we will explore each of these patterns in detail and even throw in some extra patterns for good measure, providing a comprehensive guide for enhancing our skills as React.js developers.

So, If you are bored of the “Hello World!” examples you find everywhere online, strap your seat belt and get ready to see real world illustrations.

Lets get started!

Higher-Order Components (HOC):

If you have previously seen or used withRouter from the react-router package or Connect(mapStateToProps, MapDispatchToProps) (UserPage)if using react-redux, Then you have seen an HOC in action 🔥.

HOCs pattern allows you to reuse code by wrapping one component with another. HOC is used to add functionality to an existing component without changing its code “like passing in the react router history andlocation to a component or connecting components to redux store”.

Enough with the fancy words, let’s dive into an example of how to create a simple HOC before showing some real-world advanced HOC implementations:

import React from 'react';

// Define the HOC function
const withColor = (WrappedComponent, color) => {
const ComponentWithColor = (props) => {
// Add the color prop to the wrapped component
return <WrappedComponent {...props} color={color} />;
};

return ComponentWithColor;
};

// Define a component that will be wrapped
const MyComponent = { color } => {
return <div style={{ backgroundColor: props.color }}>Hello, World!</div>;
};

// Use the HOC to create a new component
const ColoredComponent = withColor(MyComponent, 'red');

// Render the new component
const App = () => {
return <ColoredComponent />;
};

In this example, we defined an HOC function called withColor that takes a WrappedComponent and a color as arguments. The function returns a new component that adds the color prop to the WrappedComponent.

We then defined a simple component called MyComponent that will be wrapped with the HOC. The component simply renders a div with a background color based on the color prop.

Next, we used the HOC function to create a new component called ColoredComponent by passing MyComponent and the color 'red' as arguments. Finally, we rendered the new component in the App component.

With this HOC, we can easily create new components with different background colors without having to duplicate the code for MyComponent. We can simply use the withColor function to create a new component with the desired color.

This is just a simple example, but HOCs can be used for more complex functionality, such as adding authentication or data fetching to components. And that is exactly what we will explore below..

ATTENTION🚨, REAL-WORLD SCENARIO UP AHEAD!

Here’s an example of an HOC that adds authentication to a component:

import React from 'react';
import { useHistory } from 'react-router-dom';

// Define the HOC function
const withAuthentication = (WrappedComponent) => {
const ComponentWithAuthentication = (props) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const history = useHistory();

useEffect(() => {
const isAuthenticated = checkIfAuthenticated(); // A function that checks if the user is authenticated
if (isAuthenticated) {
setIsAuthenticated(true);
} else {
history.push('/login'); // Redirect the user to the login page
}
}, []);

// Render the wrapped component if the user is authenticated
// Render a message if the user is not authenticated
if (isAuthenticated) {
return <WrappedComponent {...props} />;
} else {
return <div>Please log in to access this page</div>;
}
};

return ComponentWithAuthentication;
};

// Define a component that will be wrapped
const MyComponent = (props) => {
return <div>Hello, World!</div>;
};

// Use the HOC to create a new component
const ComponentWithAuth = withAuthentication(MyComponent);

// Render the new component
const App = () => {
return <ComponentWithAuth />;
};

In this example, we defined an HOC function called withAuthentication that takes a WrappedComponent as an argument. The function returns a new component called ComponentWithAuthenticationthat checks if the user is authenticated before rendering the WrappedComponent.

We then defined a simple component called MyComponent that will be wrapped with the HOC. The component simply renders a div with the text Hello, World!.

Next, we used the withAuthentication function to create a new component called ComponentWithAuth by passing MyComponent as an argument. The new component will only render the MyComponent if the user is authenticated else the user will be redirected to login page.

YET ANOTHER REAL-WORLD EXAMPLE!

Similarly, we can also create an HOC that adds data fetching to a component. Here’s an example:

import React from 'react';

// Define the HOC function
const withDataFetching = (url) => (WrappedComponent) => {
// Define the WithDataFetching component
const WithDataFetching = (props) => {
// Declare state variables
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);

// Fetch data from the provided URL and update state with the response
useEffect(() => {
// notice how we define async functions in a useEffect hook, more on that in an upcoming article
const fetchData = async () => {
setIsLoading(true);
try {
const response = await fetch(url); // Fetch data from the provided URL
const data = await response.json(); // Extract data from the response
setData(data);
setIsLoading(false);
} catch (error) {
setError(error);
setIsLoading(false);
}
};
fetchData();
}, [url]);

// Render the wrapped component with the data and isLoading state as props
return (
<WrappedComponent
data={data}
isLoading={isLoading}
error={error}
{...props}
/>
);
};

// Return the WithDataFetching component
return WithDataFetching;
};

// Define the component that will be wrapped
const MyComponent = (props) => {
return (
<div>
{props.isLoading && <div>Loading...</div>}
{props.data && <div>Data: {props.data}</div>}
{props.error && <div>Error: {props.error}</div>}
</div>
);
};

// Use the HOC to create a new component
const ComponentWithDataFetching = withDataFetching(
'https://jsonplaceholder.typicode.com/posts/1'
)(MyComponent);

// Render the new component
const App = () => {
return <ComponentWithDataFetching />;
};

In this example, we then defined an HOC function called withDataFetching that takes a URL as an argument and returns a new function called WithDataFetchingthat takes a WrappedComponent as an argument. The function returns a new component that fetches data from the provided URL and passes the data, loading state, and error state as props to the wrapped component.

We then defined a component called MyComponent that will be wrapped with our HOC that adds data fetching functionality. The component takes three props: isLoading, data, and error.

Then, we used the withDataFetching function to create a new component called ComponentWithDataFetching by passing MyComponent as an argument and the URL to fetch data from. The new component will render MyComponent with the data, loading state, and error state as props.

Finally, we rendered the new component in the App component. When the component mounts, it will fetch data from the provided URL and pass the fetched data, loading state, and error state as props to MyComponent.

Advantages and Disadvantages of using HOCs:

While HOCs offer several advantages, they also come with some disadvantages. Here’s a comparison of the advantages and disadvantages of using HOCs in React:

Advantages:

  • Code reusability: HOCs allow you to reuse code by extracting common functionality from multiple components into a single HOC. This can reduce code duplication and make your code more maintainable.
  • Separation of concerns: HOCs allow you to separate concerns between components by isolating specific functionality within the HOC. This can make your code more modular and easier to maintain.
  • Composition: HOCs enable you to compose components in a flexible and extensible way. By wrapping components with HOCs, you can build complex and sophisticated UIs without having to write a lot of boilerplate code.
  • Flexibility: HOCs offer a lot of flexibility since they can be used with any component, regardless of its implementation. This makes it easy to apply common functionality to multiple components without modifying their implementation.

Disadvantages:

  • Prop drilling: One of the main drawbacks of using HOCs is that they can lead to prop drilling, which is the process of passing props down through several layers of components. This can make the code harder to understand and maintain.
  • Complexity: HOCs can make the code more complex and difficult to reason about, especially if there are multiple HOCs involved. This can make debugging and troubleshooting more challenging.
  • Inheritance Inversion: HOCs can invert the control of inheritance, which can make it harder to understand how components are composed and which components are responsible for rendering specific content.
  • Name collisions: HOCs can lead to name collisions, especially if the HOC itself or its props have the same name as the props of the wrapped component. This can cause unexpected behavior and make debugging more difficult.
  • Performance overhead: HOCs can add a performance overhead to the application, especially if they are used excessively or in performance-critical components.

Conclusion:

HOCs in React are quite swell,
They offer advantages, can’t you tell?
Reusable code, separation of concerns too,
Plus composition and flexibility for you.

But beware of the drawbacks they bring,
Prop drilling, name collisions — these are a thing.
Complexity and performance overhead too,
Inheritance inversion might catch you.

Consider your needs and requirements with care,
Before deciding if HOCs are really fair.
weigh the pros and cons, and then you’ll see,
If HOCs are the right choice for thee.

If you’ve reached this point “and enjoyed the rhyming conclusion 😬” please consider applauding 👏 and following my account for more content like this in the future.

--

--

Mustafa Albaghdadi
Mustafa Albaghdadi

Written by Mustafa Albaghdadi

An enthusiastic Software Engineer with a strong passion to share what I have learned in technologies such as React.js, React Native, Django and .NET.

No responses yet