React Higher Order Components — HOC

Musema Hassen
4 min readFeb 17, 2023

--

This article is assuming you have basic understanding of JavaScript and React.

  1. React: Higher Order Component(HOC) is a function which takes a react COMPONENT as param and return a COMPONENT.
  2. JavaScript: Higher Order Function(HOF) is a function which takes a FUNCTION as parameter OR returns a FUNCTION

export function withThemeContext(ComponentToWrap: React.FC): React.FC {

return function Wrapper(props): JSX.Element {
const [themeType, setThemeType] = useState<ThemeType>('light');
return (
<ThemeContext.Provider value={{ themeType, setThemeType}}>
<ComponentToWrap {...props} />
</ThemeContext.Provider>
);
};
}

There quite lot of concepts packed in this code snippet, we will try break it down.

So in order to understand React HOC you need to be comfortable with JavaScript Higher Order Functions because HOC is just a variation HOF. I recommend reading this article to understand how React Function components essentially are JavaScript functions.

withThemeContext : is a function which takes a Function( React.FC) as parameter and returns another Function( which is named Wrapper type of React.FC).

The function returned from withThemeContext is also a function, what this function does is it wraps the input component with ThemeContext that way we will be able to access the theme inside the component and it’s children components. This article is not about how React context works, if you’re not familiar with it, just imagine this withThemeContext HOC is providing some feature to the Wrapped component.

Usage

function MyComponent() {
// my component logic
}

const MyComponentWithThemeContext = withThemeContext(MyComponent);
// Now MyComponentWithThemeContext with the new Wrapper component returned from
// withThemeContext function

// app.tsx
function App(): JSX.Element {
return <MyComponentWithThemeContext />
}

// index.ts
ReactDOM.render(<App/>, document.getElementById('app'));

You might be asking why don’t we add the wrapper logic into MyComponent itself, that is possible. However imagine you have too many logics like this in your application, handling them in the same component gets complex very quickly. With this approach the HOC has only one goal, in this specific example withThemeContext has only one responsibility, managing the theme, which adheres Single Responsibility design principle.

Just to better understand the importance of this pattern, let’s assume you want to add ErrorBoundary to your component tree, to handle any unhandled exceptions in you app. What would you do? Should you change any of this components, NO. That is where HOC become handy, sometimes you can add more feature to your app without even touching any of the existing components, React Error Boundary is one of good examples. I have an article on this here if you are interested.

export function withErrorBoundary(ComponentToWrap: React.FC): React.FC {

return function Wrapper(props): JSX.Element {

return <ErrorBoundary>
<ComponentToWrap {...props} />
</ErrorBoundary>;
};
}

const CompWithThemeContext = withThemeContext(MyComponent);
const CompWithErrorBoundary = withErrorBoundary(ComponentWithThemeContext);

// did you notice how the output of the first hoc passed to the other hoc
// this mean we are able to compose independent features together to build up application logic
// DECOMPOSITION then COMPOSITION = FLEXIBILITY + REUSABILITY + MANAGABILITY

// let's say now you have one more hoc,
// just implement it independently and follow the same pattern. like so

const CompWithAnotherFeature = withAnotherFeature(MyComponent);
const CompWithThemeContext = withThemeContext(CompWithAnotherFeature);
const CompWithErrorBoundary = withErrorBoundary(ComponentWithThemeContext);
// the above three lines are equivalent to the following
const Component = withErrorBoundary(withThemeContext(withAnotherFeature(MyComponent)));

Putting all together

// types.ts
export type ThemeType = 'dark' | 'light';

export interface IThemeContext {
themeType: ThemeType;
setThemeType: (themeType: ThemeType) => void;
}
// themeContext.ts 

import { IThemeContext, ThemeType } from './types';
import React from 'react';

// create a reat context of type IThemeContext
const ThemeContext = React.createContext<IThemeContext>({} as IThemeContext);
// withThemeContext.tsx

export function withThemeContext(ComponentToWrap: React.FC): React.FC {

return function Wrapper(props): JSX.Element {
const [themeType, setThemeType] = useState<ThemeType>('light');
return (
<ThemeContext.Provider value={{ themeType, setThemeType}}>
<ComponentToWrap {...props} />
</ThemeContext.Provider>
);
};
}
import React, { useContext } from 'react';
import { ThemeContext } from './themeContext';

// This button provide the user to choose a theme
function ChangeThemeButton() {
const { setThemeType } = useContext(ThemeContext);

const onDarkClick = useCallback(() => { setThemeType('dark') }, []);
const onLightClick = useCallback(() => { setThemeType('light') }, []);

return (
<div>
<button onClick={onLightClick}>Change to light</button>
<button onClick={onDarkClick}>Change to dark</button>
</div>
);
}
// withErrorBoundary.ts
export function withErrorBoundary(ComponentToWrap: React.FC): React.FC {

return function Wrapper(props): JSX.Element {

return <ErrorBoundary> // please check my other article for ErrorBoundary
<ComponentToWrap {...props} />
</ErrorBoundary>;
};
}
// myComposer.ts

const CompWithAnotherFeature = withAnotherFeature(MyComponent);
const CompWithThemeContext = withThemeContext(CompWithAnotherFeature);
const CompWithErrorBoundary = withErrorBoundary(ComponentWithThemeContext);

export { CompWithErrorBoundary as MyComponent }; // just renaming before exporting
// app.tsx
import React from 'react';
import { MyComponent } from './myComposer'; // please note this is the renamed component

export function App() {

return <MyComponent />
}
// index.ts
import ReactDOM from 'react-dom';

ReactDOM.render(<App/>, document.getElementById('app'));

Conclusion

In this articale we discussed mainly HOC, however to try make the scenario more practical I used some other concepts which you may not familiar with. i.e. React Context API, React hooks, which will be discussing on other sessions. And there is also interesting topic about HOC vs Custom hooks. For most parts Custom Hooks can replace HOC in much better way but not completely.

Next Up: React Custom Hooks vs HOC

--

--