Within an application, it is fairly common to have components sharing functionalities. Should it be props passing, state management, or even lifecycle methods, it is not rare to have the same code repeated in multiple components. This can lead to a lot of code duplication and can make the codebase harder to maintain and understand.
In this post, we will see how wrapper components can help to improve the maintainability and reusability of your components in a React application.
What are wrapper components (or Higher Order Components)?
Wrapper components are components that will execute an action before or after rendering the children components. They are used to wrap child components and provide them with additional functionalities.
They take as an input a component that we want to add a logic to and return a new component that will have the logic added to it.
These wrapper components can be used in many cases and we will break down some of them in an instant.
Reusability & Maintainability
One of the main advantages of using wrapper components is the reusability they provide. By wrapping a component with a wrapper component, you can reuse the logic in multiple places without having to duplicate the code. This means that while proceeding to a change in the logic, you will only have to change it in one place and it will be reflected in all the components using it.
This also makes the codebase easier to understand for new developers as they will not have to understand the logic of the component, but only the logic of the wrapper component. Moreover, there is no code splitting and the logic is centralized in one place making it more readable and maintainable.
Example of wrapper component
Wrapper components have multiple use cases but let’s focus on one of them to illustrate how they can be used.
Let’s think about some data fetching. You might have multiple components that need to fetch data from an API. Instead of repeating the same logic in each component, you can create a wrapper component that will handle the data fetching and pass the data as a prop to the child component.
Here is an example of a wrapper component that fetches cat facts from an API and passes them as a prop to the child component:
src/components/WithLoading.tsx
import { useEffect, useState } from "react";
const WithLoading = (WrappedComponent, url) => {
return function WithLoadingComponent(props) {
const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
fetch(url)
.then((res) => res.json())
.then((data) => {
setData(data);
setLoading(false);
});
};
fetchData();
}, []);
return loading ? <div>Loading...</div> : <WrappedComponent {...props} data={data} />;
};
};
```
export default WithLoading;
Notice that the WithLoading
component takes a WrappedComponent
and a url
as arguments. It then returns a new component that will fetch the data from the url
and pass it as a prop to the WrappedComponent
. This is great as you can use this wrapper component to fetch data from any API and pass it to any component.
src/components/CatFacts.tsx
import WithLoading from "./WithLoading";
function CatFacts({data}) {
return (
<>
<h1>Cat Facts</h1>
<ul>
{data.map((fact) => (
<li key={fact._id}>{fact.text}</li>
))}
</ul>
</>
);
}
export default WithLoading(CatFacts, "https://cat-fact.herokuapp.com/facts");
In this example, we have a CatFacts
component that will display the cat facts. We use the WithLoading
wrapper component to fetch the data from the API and pass it as a prop to the CatFacts
component. Since we default export the component wrapped in the WithLoading
HOC, we can use it directly in our application.
Other use cases
This was only one example of how wrapper components can be used. They can be used in many other cases such as authentication, error handling, state management, etc. Here are a few real life use cases:
-
Authentication: You can create a wrapper component that will check if the user is authenticated and redirect them to the login page if they are not. (as an example, while calling an API /me route, you could check if the response is a 401 and redirect the user to the login page if it is the case.)
-
Error handling: You can create a wrapper component that will catch any error thrown by the child component and display an error message. (ex: while calling an API, you could catch the error and display a message to the user.)
-
State management: You can create a wrapper component that will manage the state of the child component and pass it as a prop. (ex: before rendering the child component get data from your
zustand
orredux
store and pass it as a prop to the child component.) -
Lifecycle methods: You can create a wrapper component that will execute a function before or after the child component is rendered. (ex: you could execute a function before rendering the child component to fetch some data.)
-
Styling: You can create a wrapper component that will add some styling to the child component. (ex: Adding a global style object to your app.)
or even
- Logging: By wrapping a component with a wrapper component, you can log the props and state of the child component. This can be useful for debugging purposes.
Conclusion
Wrapper components are a great way to improve the maintainability and reusability of your components in a React application. They allow you to reuse logic in multiple places without having to duplicate the code. This makes the codebase easier to maintain and understand for new developers. Moreover, it centralizes the logic in one place making it more readable and maintainable.
There are plenty of use cases for wrapper components and they can be used in many different situations. They are a great tool to have in your toolbox and can help you to write cleaner and more maintainable code.
We have not talked much about it yet but wrapper components can be nested. This means that you can have a wrapper component that wraps another wrapper component. This can be useful to add multiple functionalities to a component without having to duplicate the code.
Downsides of wrapper components
While wrapper components can be very useful, they can also be overused. It is important to keep in mind that the more wrapper components you have, the more complex your application will become. It is important to find the right balance between reusability and complexity. The more wrapper components you have, the harder it will be to understand the logic of your application, and the harder it will be to debug.
Moreover using Wrapper Components might lead to some naming collisions. As an example if you used the cat facts example shown earlier but made it so that a cat image (why not?) was passed down as a prop with the name data
, you would have a naming collision with the data
prop that is used to pass the data from the wrapper component to the child component. This would result in data being overwritten and the child component not working as expected (and maybe some debugging time).
Concerns can be emitted about the performance of the application. Wrapper components can lead to a lot of re-renders and can slow down the application if they are nested too much, as they add layers to the application. It is important to keep an eye on the performance of your app and to make sure that the wrapper components are not causing any performance issues.
Finally, it is important to keep in mind that wrapper components are not always the best solution. Sometimes it is better to duplicate the code than to use a wrapper component.
React hooks vs HOC
When is it HOC instead of React Hooks? Firstly when a same, uncustomized behavior is common in your app and used by plenty of components. Also, when your components can work without any knowledge of the behavior. In the case of the cat facts example, the CatFacts
component does not need to know that the data is fetched from an API. It only needs to know that it will receive the data as a prop.
However in cases of a custom behavior, or a behavior that is not shared widely, it is better to use React Hooks. They are also easier to test and can be used to create more complex behaviors.
Thank you for reading this post. I hope you enjoyed it and that it will help you to have a wider toolbox to write cleaner and more maintainable code.