June 4, 2024•6 min read••
Tags ▼
- react
- higher order components
- jsx
- hocs
![]()
Higher-order components (HOCs) are one of those concepts in React that might seem a bit confusing at first. But once you get the hang of them, they can be incredibly powerful. Essentially, an HOC is a function that takes a component and returns a new component. This might sound a bit abstract, so let's break it down.
Most React components return JSX, which represents the DOM elements that should be rendered. HOCs, on the other hand, return another component. Think of them as component factories: functions that create new components. This extra step allows us to add additional behavior or functionality to existing components without modifying them directly.
One of the main reasons to use HOCs is to share behavior between multiple components. This is similar to what we do with container components, where we wrap different components in the same container to give them similar behavior. HOCs allow us to do this in a more flexible and reusable way.
Another use case for HOCs is to add extra functionality to an existing component. For example, if you have a component built by someone else and you want to add new features to it, you can use an HOC to wrap the original component and extend its capabilities.
Let's start with a simple example: creating an HOC that prints the props of a component. We'll call this HOC printProps. Here's how it works:
printProps.js.export const printProps = (Component) => { return (props) => { console.log(props); return <Component {...props} />; }; };
To use this HOC, import it into your main file and wrap your component with it:
import { printProps } from './printProps'; import UserInfo from './UserInfo'; const UserInfoWithProps = printProps(UserInfo); <UserInfoWithProps a={1} b="hello" c={{ name: 'Shaun' }} />;
When you run this code, you'll see the props logged in the console. This is a simple example, but it shows how HOCs can be used to add functionality to components.
Next, let's create an HOC that loads data from a server and passes it to a component. We'll call this HOC withUser. It will fetch user data based on an ID and pass it as a prop to the wrapped component.
withUser.js.useState and useEffect to manage and fetch user data.import React, { useState, useEffect } from 'react'; import axios from 'axios'; export const withUser = (Component, userId) => { return (props) => { const [user, setUser] = useState(null); useEffect(() => { const fetchUser = async () => { const response = await axios.get(`/users/${userId}`); setUser(response.data); }; fetchUser(); }, [userId]); return <Component {...props} user={user} />; }; };
To use this HOC, import it and wrap your component:
import { withUser } from './withUser'; import UserInfo from './UserInfo'; const UserInfoWithLoader = withUser(UserInfo, 234); <UserInfoWithLoader />;
This will load user data for user ID 234 and pass it to UserInfo as a prop.
Now let's take it a step further and create an HOC that not only loads data but also allows editing it. We'll call this HOC withEditableUser.
withEditableUser.js.useState and useEffect to manage and fetch user data.import React, { useState, useEffect } from 'react'; import axios from 'axios'; export const withEditableUser = (Component, userId) => { return (props) => { const [originalUser, setOriginalUser] = useState(null); const [user, setUser] = useState(null); useEffect(() => { const fetchUser = async () => { const response = await axios.get(`/users/${userId}`); setOriginalUser(response.data); setUser(response.data); }; fetchUser(); }, [userId]); const onChangeUser = (changes) => { setUser({ ...user, ...changes }); }; const onSaveUser = async () => { const response = await axios.post(`/users/${userId}`, user); setOriginalUser(response.data); setUser(response.data); }; const onResetUser = () => { setUser(originalUser); }; return ( <Component {...props} user={user} onChangeUser={onChangeUser} onSaveUser={onSaveUser} onResetUser={onResetUser} /> ); }; };
To use this HOC, import it and wrap your component:
import { withEditableUser } from './withEditableUser'; import UserInfoForm from './UserInfoForm'; const UserInfoFormWithEditable = withEditableUser(UserInfoForm, 123); <UserInfoFormWithEditable />;
This will allow editing user data for user ID 123.
Finally, let's generalize our withEditableUser HOC to work with any resource. We'll call this new HOC withEditableResource.
withEditableResource.js.withEditableUser.import React, { useState, useEffect } from 'react'; import axios from 'axios'; export const withEditableResource = (Component, resourcePath, resourceName) => { return (props) => { const [originalData, setOriginalData] = useState(null); const [data, setData] = useState(null); useEffect(() => { const fetchData = async () => { const response = await axios.get(resourcePath); setOriginalData(response.data); setData(response.data); }; fetchData(); }, [resourcePath]); const onChange = (changes) => { setData({ ...data, ...changes }); }; const onSave = async () => { const response = await axios.post(resourcePath, { [resourceName]: data }); setOriginalData(response.data); setData(response.data); }; const onReset = () => { setData(originalData); }; return ( <Component {...props} {...{ [resourceName]: data }} {...{ [`onChange${capitalize(resourceName)}`]: onChange }} {...{ [`onSave${capitalize(resourceName)}`]: onSave }} {...{ [`onReset${capitalize(resourceName)}`]: onReset }} /> ); }; }; const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
To use this generalized HOC:
import { withEditableResource } from './withEditableResource'; import UserInfoForm from './UserInfoForm'; const UserInfoFormWithResource = withEditableResource(UserInfoForm, '/users/123', 'user'); <UserInfoFormWithResource />;
This will allow editing any resource by specifying its path and name.
Higher-order components are powerful tools in React for sharing behavior and adding functionality to components in a reusable way. Once you understand how they work, you'll find many opportunities to use them in your projects.
Follow on your preferred channel for new articles, notes, and experiments.