Higher Order Components in React: A Practical Guide

When you build an internal support dashboard, the same requirements often appear in several places. One page needs an authentication check. Another needs a loading state. A third needs activity tracking or role-based access.

You could repeat that logic inside every React component. That works at first, but it quickly creates bulky files and inconsistent behavior. I have seen this happen often in customer portals and admin applications where shared rules grow over time.

A Higher Order Component in React helps you reuse that shared behavior without rewriting each screen. You will build one around a support ticket dashboard and learn where HOCs still fit in modern React applications.

What Is a Higher Order Component in React?

A Higher Order Component, usually called an HOC, is a function that takes a React component and returns a new enhanced component.

It does not change the original component directly. Instead, it wraps that component and adds shared logic around it.

The basic pattern looks like this:

const withSomething = (WrappedComponent) => {
return function EnhancedComponent(props) {
return <WrappedComponent {...props} />;
};
};

The withSomething function receives a component, such as TicketList. It returns EnhancedComponent, which renders the original component and passes all props through with the spread syntax.

The name usually starts with with. This makes its purpose clear when you read code, such as withAuth, withLoading, or withPermission.

If you need a refresher on passing data into components, review how props work in React. Clean prop handling matters because HOCs often add or transform props.

Why Use Higher Order Components in React?

HOCs work best when several screens need the same cross-cutting behavior. Cross-cutting means logic that applies across unrelated features, such as access control, loading UI, analytics, or error display.

For example, imagine a browser-based support ticket dashboard with these pages:

  • Open tickets
  • Escalated tickets
  • Resolved tickets
  • Customer details

Each page may call an API and need the same loading message. Without an HOC, every page repeats an isLoading check and loader markup.

A Higher Order Component in React keeps that behavior in one place. The screen components can focus on their own JSX, data layout, and event handlers.

HOCs are not the default choice for every shared feature in modern React 18+. Custom hooks usually handle reusable stateful logic more cleanly. Still, HOCs remain useful when you need to wrap a component with a consistent user interface or add a shared prop contract.

Build a Ticket List Component

Start with a focused TicketList component. It receives ticket data through props and renders it in a simple table.

function TicketList({ tickets, title }) {
return (
<section>
<h2>{title}</h2>

{tickets.length === 0 ? (
<p>No tickets found.</p>
) : (
<table>
<thead>
<tr>
<th>Ticket</th>
<th>Customer</th>
<th>Priority</th>
</tr>
</thead>
<tbody>
{tickets.map((ticket) => (
<tr key={ticket.id}>
<td>{ticket.subject}</td>
<td>{ticket.customer}</td>
<td>{ticket.priority}</td>
</tr>
))}
</tbody>
</table>
)}
</section>
);
}

This component does one job: display tickets. The conditional expression is conditional rendering, which shows a helpful empty message when the array has no items.

The stable ticket.id is used as the React list key. Avoid an array index because sorting or filtering can make React associate the wrong row with the wrong item.

For more reusable screen design, see this guide to component-based architecture in React.

Create a Loading HOC

Next, create an HOC that displays a loading message before rendering the ticket list. This is useful when an API request is still running.

function withLoading(WrappedComponent) {
function WithLoading({ isLoading, ...props }) {
if (isLoading) {
return <p role="status">Loading support tickets...</p>;
}

return <WrappedComponent {...props} />;
}

WithLoading.displayName = `withLoading(${
WrappedComponent.displayName || WrappedComponent.name || "Component"
})`;

return WithLoading;
}

withLoading receives a component and returns WithLoading. The wrapper extracts isLoading, then forwards every other prop to the wrapped component.

The displayName gives browser developer tools a useful name. Without it, a component tree may show only anonymous wrapper functions, which makes debugging slower.

Now apply the HOC to TicketList:

const TicketListWithLoading = withLoading(TicketList);

function SupportDashboard() {
const tickets = [
{
id: "SUP-101",
subject: "Unable to reset password",
customer: "Maya Patel",
priority: "High",
},
{
id: "SUP-102",
subject: "Invoice download failed",
customer: "Arjun Shah",
priority: "Medium",
},
];

const isLoading = false;

return (
<TicketListWithLoading
title="Open Support Tickets"
tickets={tickets}
isLoading={isLoading}
/>
);
}

SupportDashboard renders the enhanced component instead of TicketList directly. When isLoading is true, users see the loading state. When it becomes false, the original ticket list receives title and tickets normally.

This keeps the data display component simple. It also makes the loading experience consistent across several dashboard pages.

Pro Tip: In my experience, the most common HOC mistake is forgetting to forward unused props. Always use {...props} unless you intentionally want the wrapper to block or replace a prop.

Use Higher-Order Components for Access Control

A second common use is protecting internal routes or sections by user role. For example, only supervisors may view escalated tickets.

Here is a small withPermission HOC:

function withPermission(WrappedComponent, requiredRole) {
function WithPermission({ currentUser, ...props }) {
const hasPermission = currentUser?.role === requiredRole;

if (!hasPermission) {
return (
<section>
<h2>Access denied</h2>
<p>You do not have permission to view this page.</p>
</section>
);
}

return <WrappedComponent {...props} />;
}

WithPermission.displayName = `withPermission(${
WrappedComponent.displayName || WrappedComponent.name || "Component"
})`;

return WithPermission;
}

This HOC checks currentUser before it renders the wrapped screen. Optional chaining (?.) prevents an error if the user object is temporarily unavailable.

Use it like this:

const EscalatedTicketList = withPermission(TicketList, "supervisor");

function EscalationsPage() {
const currentUser = {
name: "Priya Nair",
role: "supervisor",
};

const escalatedTickets = [
{
id: "SUP-202",
subject: "Payment charged twice",
customer: "Northwind Traders",
priority: "Critical",
},
];

return (
<EscalatedTicketList
currentUser={currentUser}
title="Escalated Tickets"
tickets={escalatedTickets}
/>
);
}

The ticket list knows nothing about security rules. It only receives display-related props. The wrapper owns the authorization decision and returns an access-denied state when needed.

This pattern can complement route protection, but it should not be your only security layer. The backend must also verify permissions before returning private data.

If this logic is tied to page navigation, learn how a protected route component works in React.

Compose Multiple HOCs Carefully

One component can use more than one HOC. For example, the escalated ticket list may need both a loading state and a permission check.

const ProtectedEscalatedTicketList = withPermission(
withLoading(TicketList),
"supervisor"
);

function EscalationsPage() {
const currentUser = {
name: "Priya Nair",
role: "supervisor",
};

const isLoading = false;
const escalatedTickets = [
{
id: "SUP-202",
subject: "Payment charged twice",
customer: "Northwind Traders",
priority: "Critical",
},
];

return (
<ProtectedEscalatedTicketList
currentUser={currentUser}
isLoading={isLoading}
title="Escalated Tickets"
tickets={escalatedTickets}
/>
);
}

The inner withLoading(TicketList) runs first. Then withPermission wraps the loading-enabled component. The permission check therefore happens before the loading state is displayed.

This order matters. A user without permission should see an access message rather than a loader for a page they cannot open.

Keep wrapper chains short. When three or four HOCs appear together, debugging props becomes harder. That is often a signal to use a custom hook, split a component, or use a shared layout component instead.

Add API Data Without Hiding Responsibilities

You can create an HOC that fetches tickets, but use it with care. A wrapper that handles API calls, permissions, caching, transformations, and rendering becomes difficult to test.

For a small internal application, this HOC can inject ticket data into a presentational component:

import { useEffect, useState } from "react";

function withTickets(WrappedComponent) {
function WithTickets(props) {
const [tickets, setTickets] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState("");

useEffect(() => {
let isActive = true;

async function loadTickets() {
try {
const response = await fetch("/api/tickets");

if (!response.ok) {
throw new Error("Unable to load tickets.");
}

const data = await response.json();

if (isActive) {
setTickets(data);
}
} catch (requestError) {
if (isActive) {
setError(requestError.message);
}
} finally {
if (isActive) {
setIsLoading(false);
}
}
}

loadTickets();

return () => {
isActive = false;
};
}, []);

if (error) {
return <p role="alert">{error}</p>;
}

return (
<WrappedComponent
{...props}
tickets={tickets}
isLoading={isLoading}
/>
);
}

return WithTickets;
}

This wrapper uses the useState hook to track ticket data, loading status, and errors. It uses useEffect to start the API request after the component renders in the browser.

The cleanup function changes isActive to false if the component unmounts before the request ends. That prevents late state updates after a user navigates away.

You can now apply it with the loading HOC:

const TicketDashboardList = withTickets(withLoading(TicketList));

function OpenTicketsPage() {
return <TicketDashboardList title="Open Support Tickets" />;
}

You can refer to the screenshot below to see the output.

Higher Order Components in React

OpenTicketsPage stays very small. withTickets supplies the API data and loading status, while withLoading decides whether to show the table or the loading message.

For API issues where the UI stays blank, this article on React fetch data not displaying in a component is a useful next read.

HOCs vs Custom Hooks

Both HOCs and custom hooks reuse logic. The main difference is where the reuse happens.

ApproachBest forHow it works
Higher Order ComponentReusing a wrapper UI, access gate, or injected propsWraps one component and returns another component
Custom hookReusing stateful logic in functional componentsCalls shared logic directly inside a component
Shared componentReusing visible layout and markupRenders the same UI through composition

For example, useTickets() is often easier than withTickets() in new React code. The component can call the hook directly and decide how it wants to render loading and error states.

Use an HOC when the same wrapper behavior should be enforced consistently. Use a custom hook when a component needs flexible control over the UI. For shared application-level state, you may also use React Context with functional components.

Things to Keep in Mind

  • Forward props: Pass unrelated props through to the wrapped component, or it may silently lose required values.
  • Set a display name: Assign displayName so React DevTools clearly identifies your enhanced components during debugging.
  • Avoid deep wrapper chains: Multiple nested HOCs make component trees and prop flow difficult to understand.
  • Protect data on the server: A permission HOC improves the frontend experience, but backend APIs must enforce authorization too.
  • Keep API logic focused: Do not let one HOC manage fetching, caching, permissions, formatting, and UI rules together.
  • Prefer hooks for flexible logic: Use a custom hook when different components need the same data behavior but different layouts.

Frequently Asked Questions

What is a Higher-Order Component in React?

A Higher-Order Component is a function that receives a React component and returns a new component. The new component can add logic, inject props, or conditionally render the original component.

Are Higher-Order Components still used in React?

Yes, HOCs are still used, especially in older codebases and reusable component libraries. In new functional React applications, custom hooks are often preferred for sharing stateful logic.

Can an HOC use React hooks?

Yes. The component returned by an HOC is a normal functional component, so it can use hooks such as useState and useEffect. Do not call hooks in the outer HOC function itself.

How do I pass props through a Higher Order Component?

Destructure any props the HOC needs, then forward the remaining props with {...props}. This prevents the wrapper from accidentally blocking props that the original component needs.

Can I use multiple Higher-Order Components on one component?

Yes. You can wrap a component with multiple HOCs, but order changes behavior. Keep the chain short and test the result so authorization, loading, and error states appear in the correct order.

Do Higher Order Components replace React Context?

No. An HOC wraps a specific component, while React Context shares values across many components without manually passing props. Context is better for application-wide data such as the current theme or authenticated user.

Higher-Order Components in React give you a clean way to reuse wrapper behavior such as loading states, permissions, and API-driven data. Start with one focused HOC, forward props carefully, and only add more wrappers when they genuinely simplify the application.

You May Also Like

I hope you found this article helpful.

Leave a Comment

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.