I agree with siraj, strictly speaking the example in the accepted answer is not a true HOC. The distinguishing feature of a HOC is that it returns a component, whereas the PrivateRoute
component in the accepted answer is a component itself. So while it accomplishes what it set out to do just fine, I don’t think it is a great example of a HOC.
In the functional component world, the most basic HOC would look like this:
const withNothing = Component => ({ ...props }) => (
<Component {...props} />
);
Calling withNothing
returns another component (not an instance, that’s the main difference), which can then be used just like a regular component:
const ComponentWithNothing = withNothing(Component);
const instance = <ComponentWithNothing someProp="test" />;
One way to use this is if you want to use ad-hoc (no pun intended lol) context providers.
Let’s say my application has multiple points where a user can login. I don’t want to copy the login logic (API calls and success/error messages) across all these points, so I’d like a reusable <Login />
component. However, in my case all these points of login differ significantly visually, so a reusable component is not an option. What I need is a reusable <WithLogin />
component, which would provide its children with all the necessary functionality – the API call and success/error messages. Here’s one way to do this:
// This context will only hold the `login` method.
// Calling this method will invoke all the required logic.
const LoginContext = React.createContext();
LoginContext.displayName = "Login";
// This "HOC" (not a true HOC yet) should take care of
// all the reusable logic - API calls and messages.
// This will allow me to pass different layouts as children.
const WithLogin = ({ children }) => {
const [popup, setPopup] = useState(null);
const doLogin = useCallback(
(email, password) =>
callLoginAPI(email, password).then(
() => {
setPopup({
message: "Success"
});
},
() => {
setPopup({
error: true,
message: "Failure"
});
}
),
[setPopup]
);
return (
<LoginContext.Provider value={doLogin}>
{children}
{popup ? (
<Modal
error={popup.error}
message={popup.message}
onClose={() => setPopup(null)}
/>
) : null}
</LoginContext.Provider>
);
};
// This is my main component. It is very neat and simple
// because all the technical bits are inside WithLogin.
const MyComponent = () => {
const login = useContext(LoginContext);
const doLogin = useCallback(() => {
login("a@b.c", "password");
}, [login]);
return (
<WithLogin>
<button type="button" onClick={doLogin}>
Login!
</button>
</WithLogin>
);
};
Unfortunately, this does not work because LoginContext.Provider
is instantiated inside MyComponent
, and so useContext(LoginContext)
returns nothing.
HOC to the rescue! What if I added a tiny middleman:
const withLogin = Component => ({ ...props }) => (
<WithLogin>
<Component {...props} />
</WithLogin>
);
And then:
const MyComponent = () => {
const login = useContext(LoginContext);
const doLogin = useCallback(() => {
login("a@b.c", "password");
}, [login]);
return (
<button type="button" onClick={doLogin}>
Login!
</button>
);
};
const MyComponentWithLogin = withLogin(MyComponent);
Bam! MyComponentWithLogin
will now work as expected.
This may well not be the best way to approach this particular situation, but I kinda like it.
And yes, it really is just an extra function call, nothing more! According to the official guide:
HOCs are not part of the React API, per se. They are a pattern that emerges from React’s compositional nature.