TypeScript Higher-Order Components: Best Practices for Writing Scalable and Type-Safe React Apps

Vasil Rashkov
4 min readMar 22, 2023

Higher-Order Components (HOCs) in React are functions that accept a component as input and return a new component with additional functionality. In this article, we’ll explore the concept of HOCs, how to create one, and how to use TypeScript to create type-safe HOCs.

What are Higher-Order Components?

A Higher-Order Component (HOC) is a function that takes a React component as input and returns a new component with additional functionality. HOCs are commonly used in React for code reuse, as they allow you to encapsulate and share common functionality between components.

HOCs can be used to implement features like authentication, routing, data fetching, and more. For example, you might have a component that requires authentication before it can be rendered. Instead of writing authentication logic in every component that needs it, you can create an HOC that handles authentication and wrap it around any component that requires authentication.

Here's an example of an HOC that adds a "loading" state to a component:

function withLoading<P extends object>(
WrappedComponent: React.ComponentType<P>
): React.FC<P & { isLoading: boolean }> {
const WithLoading: React.FC<P & { isLoading: boolean }> = ({ isLoading, ...props }: { isLoading: boolean } & P) => {
if (isLoading) {
return <div>Loading...</div>;
}

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

return WithLoading;
}

This HOC accepts a component as input and returns a new component that adds a "loading" state to the original component. The new component checks the "isLoading" prop and either renders a "Loading..." message or passes the props to the original component.

To use this HOC, you can wrap any component that requires a "loading" state:

const MyComponent: React.FC = ({ name }: { name: string }) => (
<div>{name}</div>
);

const MyComponentWithLoading = withLoading(MyComponent);

const App: React.FC = () => (
<MyComponentWithLoading isLoading={true} name="John Doe" />
);

In this example, we wrap the MyComponent component with the withLoading HOC to create a new component called MyComponentWithLoading. We then render the new component in the App component with an isLoading prop.

Creating a TypeScript Higher-Order Component

TypeScript is a powerful tool for building scalable and type-safe applications in React. It allows you to catch errors at compile-time instead of runtime, which can save you a lot of time and headaches.

To create a TypeScript HOC, you'll need to use TypeScript's generic types to specify the props of the input component and the additional props of the output component. Here's an example of a TypeScript HOC that adds a "theme" prop to a component:

interface WithThemeProps {
theme: string;
}

function withTheme<P extends object>(
WrappedComponent: React.ComponentType<P>
): React.FC<P & WithThemeProps> {
const WithTheme: React.FC<P & WithThemeProps> = ({ theme, ...props }: WithThemeProps & P) => (
<WrappedComponent {...props as P} theme={theme} />
);

return WithTheme;
}

In this example, we define an interface called WithThemeProps that specifies the theme prop. We then use TypeScript’s generic types to specify the props of the input component (P) and the additional props of the output component (WithThemeProps). The WithTheme component extracts the theme prop and passes the rest of the props to the original component.

To use this HOC, you can wrap any component that requires a "theme" prop:

interface MyComponentProps {
name: string;
}

const MyComponent: React.FC<MyComponentProps> = ({ name, theme }: MyComponentProps & WithThemeProps) => (
<div style={{ background: theme }}>
{name}
</div>
);

const MyComponentWithTheme = withTheme(MyComponent);

const App: React.FC = () => (
<MyComponentWithTheme name="John Doe" theme="lightblue" />
);

In this example, we define a MyComponent component that requires a name prop and a theme prop. We then wrap the component with the withTheme HOC to create a new component called MyComponentWithTheme. We then render the new component in the App component with a name prop and a theme prop.

Note that the type of the MyComponent component includes both the MyComponentProps and the WithThemeProps interfaces. This allows us to access both the name and theme props in the component.

Combining Multiple Higher-Order Components

You can also combine multiple HOCs to create more complex components. To do this, you can simply wrap the input component with multiple HOCs.

Here's an example of an HOC that combines the withLoading and withTheme HOCs:

function withLoadingAndTheme<P extends object>(
WrappedComponent: React.ComponentType<P>
): React.FC<P & WithLoadingProps & WithThemeProps> {
const WithLoadingAndTheme: React.FC<P & WithLoadingProps & WithThemeProps> = ({ isLoading, theme, ...props }: WithLoadingProps & WithThemeProps & P) => (
<WrappedComponent {...props as P} isLoading={isLoading} theme={theme} />
);

return withTheme(withLoading(WithLoadingAndTheme));
}

This HOC combines the withLoading and withTheme HOCs by wrapping the input component with WithLoadingAndTheme, withTheme, and withLoading.

To use this HOC, you can wrap any component that requires a "loading" state and a "theme" prop:

const MyComponent: React.FC = ({ name }: { name: string }) => (
<div>{name}</div>
);

const MyComponentWithLoadingAndTheme = withLoadingAndTheme(MyComponent);

const App: React.FC = () => (
<MyComponentWithLoadingAndTheme isLoading={true} theme="lightblue" name="John Doe" />
);

In this example, we wrap the MyComponent component with the withLoadingAndTheme HOC to create a new component called MyComponentWithLoadingAndTheme. We then render the new component in the App component with an isLoading prop, a theme prop, and a name prop.

Conclusion

In this article, we've explored the concept of Higher-Order Components (HOCs) in React and how to create them using TypeScript. We've also seen how to combine multiple HOCs to create more complex components.

HOCs are a powerful tool for code reuse in React, as they allow you to encapsulate and share common functionality between components. By using TypeScript, you can create type-safe HOCs that catch errors at compile-time instead of runtime, which can save you a lot of time and headaches.

I hope this article has been helpful in understanding HOCs and TypeScript in React. If you have any questions or feedback, feel free to leave a comment below!

--

--

Vasil Rashkov

Senior Software Engineer. Running a software agency on the side.