I always wondered that why the official react dev has removed the part of HOCs from it's documentation until I seek and found the answer in it's legacy docs:
'Higher-order components are not commonly used in modern React code' — official react dev
Now, we can only access the legacy docs to view it but sadly with it's old but gold obsolete class syntax.
W3schools is another alternative(with only 1 example which is insufficient to grasp things) alongside the indie blogs. So far, I've seen that the given portion of the contents wasn't included, whether in typescript or in a concrete deep dive.
Just because of my curiosity, I've gone through and grasp every use case of React HOCs, every potential replacement for HOCs, every tricks to remove the unnecessary workarounds for the app that might be a good fit for HOCs. I assure you, you're going to have A LOT of takeaways by the end of this article and maybe refute the claim of react docs(see above) — who knows?
What is HOC in React?
Concretely, "a higher-order component is a function that takes a component and returns a new component" says the legacy docs. When we apply a HOC to a component, though, the original component is wrapped with a dedicated container component.
The HOCs are commonly used for:
- Authentication
- Validation
- Redirection
- Database Queries/Actions
Pretty much everything on the server. But I'd like to encourage you to use it widely not only on the server side but the client side as well. Add it on your checklist I addressed about that later on as well.
Practicing with a HOC in React
When it comes to using a HOC with typescript, everything slightly changes due to the nature of .tsx syntax. I want you to never forget the first rule I understood so far — We always need to consider of the given and taken props.
Writing our First HOC Wrapper for Auth
The most basic way to digest underlying behavior of the HOCs passes throughout the very common usage scenario of the HOC which is the authorization. Suppose that we're working on this directory inside a latest version of Next.js/React app:
Assuming we haven't utilized the HOC and have written our own validation logic for the auth manually. Thus, the post/[postId]/page.tsx component roughly would have looked like this:
Messy.. Especially if you have abstracted stuffs in your codebase.
Of course you can write a server function that handles all these stuff externally where it's simply being passed to each page component to do the same thing, it's also an alternative.
In this scenario, the primary goal of the HOCs would be preventing iterations over the same logic for other components wherepostIdis being consumed. It's quite overwhelming though to write the same logic.
Use it with caution: If your app doesn't scale, meaning the logic above does not repeat itself across your app components, the HOC might be an overhead rather than a solution. Assess the overall amount of required fields by checking your codebase and detecting the components that shares the same logic. Make your decision only after that for a proper React HOC replacement.
So instead, we can create a wrapper HOC function encapsulating the inner logic to not repeat ourselves by respecting the DRY principle.
First HOC Variation with Passing Props to the Downstream
Below, we have a complete HOC to handle the auth logic when working with our React components and beyond that, we're passing the props to whatever the wrapped component is.
Breaking the logic down:
- Ensuring that we allowed proper types when we define the HOC.
- P extends object ensures that we only allow any kind of prop that is object otherwise warn the user back at compile time about that. Since a React component doesn't work with primitives other than object, we will be justified with the extending method of the generic type.
- The wrapped component's signature (props: P & Props) => Promise<React.ReactNode> forces every consumer of withAuth to declare session and user as accepted props. The typescript will automatically infer it's type at the call site which is a huge plus.
- Don't let the auth HOC know about the resource(the part where we defined object in the type above) unless you strictly need an abstracted dedicated layer.
Takeaway: withAuthhas no business validating postId. Auth answers "is this actor allowed to make a request at all?" resource validation answers "does this specific resource exist or is it in a valid state?" Fusing them means every new component that needs auth and has its own validation rule (validatePost, validateComment, whatever comes next) forces us to either fork the HOC or bolt conditional logic onto it defeating the DRY goal we started with... We don't want that, do you want? I don't want.
Second HOC Variation with Passing 'Nothing'
In this time we simply won't pass anything and keep the rest context as-is.
I don't think that I need to break down things like I did for the first variation and it's pretty much the same steps except passing and inferring the props of wrapped component.
Better to see how it's implemented over the components.
HOC Implementation on our React Components
The most common pattern to use the HOC in our components (in my case it's a page comp.) is to define it outside of the exported component:
We could definitely move the validation logic right inside the Post component because now it looks annoying and awkward I know sorry about that 😅
Let's clean it up and assume the post component is a server component where the validatePost() does it's job inside of the <Post /> component:
We can achieve that by simply extending the post props with the HOC type that we wrote earlier:
Apparently huge difference compared to the given very first example that doesn't scale.
Scaling up the Implementation
Now we're about the see extensive usage(one of my favorite one) of the HOCs. I told you that HOCs are highly scalable. To prove that lets add one more layer to our logic.
I guess a role based access control(RBAC) logic is a perfect candidate to append in this stuff as we're working on the posts so we're going to need to check either it's an admin or editor.
Considering the second variation of the HOC that passes nothing to the wrapper component, similarly we can create one more HOC called withRole that doesn't depend on anything:
No dependency, no undergoing props, pure component. The only point that has to be written with caution is the first returned react node type wrapped with Promise:
This type represents characteristics of our wrapped function. If it's a client component therefore we can turn it into:
If we don't know the returned component or basically doesn't care we can create a generic type allowing the returned node to be client or server like this:
The second remains unchanged nevertheless as it's belong the internal HOC component and running an async function.
Finally, the implementation of withRole inside our page component:
It's ridiculously simple to attach it into React components.
Execution Order of the HOCs
The execution order always starts from the outermost wrapper HOC and slightly moves to the right side of the line.
Speaking of the latest version of our HOC component, let's extend it with one more HOC to illustrate the communication better:
In this case, withAuth runs first, withRole second, withAnalytics third and the Post last.

The order never changes always from outermost to innermost. We could even simplify it an make it more readable by adding a composer. Like this:
Interconnected React HOCs
An additional detail that might be worth to share with you folks is that we worked on HOCs which doesn't share anything among themselves. Each remains standalone, unaware of whatever happens on the wrapped component, only responsible to validate and return.
There's another scenario where the HOCs shares data to each other. In that case, we must remember what I have said the beginning
We always need to consider of the given and taken props.
withRole and withAuth extended only object and passed the fetched variable arbitrarily to the wrapped component. Realistically, withAuth would not have needed a prop inherintly but I can't say the same for withRole. In a real app, it should get a parameter say userId then derive something from it or use it directly in a DB function.
In this particular scenario, we have to change parameters of withRole roughly something to this:
Conclusion
HOCs aren't a pattern you reach for by default yet they're a pattern you arrive at, once the same auth check, role check, or tracking snippet starts showing up in one too many files. That repetition is the actual signal to look for, not "HOCs are best practice.". And folks, remember the outermost HOC always runs first. This was the The Complete Guide to Higher-Order Components in Next.js React With Typescript
Thanks for reading 👋
I got to sleep it's 4.32 PM.
