DEV Community

Justin
Justin

Posted on

Using React.forwardRef() and an HOC on the same component

Focus management in React currently has one solution: refs. If you want a function component to accept a ref, you should use React.forwardRef(). For a basic Input component, it would look like this:

import React from "react";

const Input = React.forwardRef(function Input({name, type, id, disabled, ...props}, ref) {
  return (
    <input
      {...props}
      name={name}
      id={id}
      disabled={disabled}
      type={type}
      ref={ref} />
  );
});

export default Input;

That's great. But what if we wanted to wrap it in an HOC? Maybe we have an HOC, shared between different form controls, for handling status messages. Let's call is withStatusMessages(). Normally, we would do something like this:

export default withStatusMessages(Input);

Everything will compile, but our ref stops working and we'll see an error in the console about function components not accepting refs.

What happened?

Remember, the component passed to React.forwardRef() needs to accept two parameters, with the second one being the ref. But our HOC doesn't know that, it just accepts and passes on props. We could update our HOC to pass on refs, but we might want it to be used with components that don't accept refs. So what can we do?

We've already decided that we can't apply the HOC after React.forwardRef() which means we have to apply the HOC before React.forwardRef(). We can't just have withStatusMessages(React.forwardRef()) because then our HOC will still drop the ref and not pass it on. What we need is a way to have the ref from React.forwardRef() passed on to the component via props (instead of as a second argument). Here's what I've come up with:

const Input = withStatusMessages(function Input({
  name,
  type,
  id,
  disabled,
  inputRef,
  ...props
}) {
  return (
    <input
      {...props}
      name={name}
      id={id}
      disabled={disabled}
      type={type}
      ref={inputRef}
    />
  );
});

export default React.forwardRef((props, ref) => {
  return <Input {...props} inputRef={ref} />;
});

We pass in the ref as the inputRef prop and then attach it to the input as normal. Now we get to use both React.forwardRef() and an HOC on the same component.

Note that we have to rename the prop, we can't just keep ref. In other words, we can't do this:

export default React.forwardRef((props, ref) => {
  return <Input {...props} ref={ref} />;
});

If you do, you'll get the error about function components not accepting refs because ref is handled specially by React (I don't know why, it sure would be nice if it just worked).

I created a working example of using this technique. And if you use TypeScript, the types are not straightforward so I've got you covered with the same example in TypeScript.

Do you know of a better way to handle this situation? I'd love to hear it.

Oldest comments (4)

Collapse
 
hw_tech profile image
Harsh Wardhan

Awesome quick read about HOC and ref issue. I was going through React documentation on Forwarding refs in HOC but could not understand their example properly. But your example helped a lot.

Thank you Justin for sharing this along with the code sandbox links for both the plain React & in typescript.

Collapse
 
marien_db profile image
Marien den Besten • Edited

The trick is to also incorporate 'forwardRef' in the HOC.

function withHookHOC(Component) {
    return forwardRef(({...props}, ref) => {
         return <Component {...props}
                          ref={ref}/>
    });
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
carlenecannonconner profile image
Carlene Cannon-Conner

Exactly what I needed. Great example, thank you for the post :D

Collapse
 
flyon profile image
René Verheij

Same here, exactly what I needed. Could not find any answers elsewhere and this worked perfectly for me. Thank you!