How can I change this class base higher order component into a functional component?

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.

Leave a Comment