react icon indicating copy to clipboard operation
react copied to clipboard

New hook; useForwardRef

Open lejahmie opened this issue 3 years ago • 2 comments

When working with React.forwardRef it can be kind of a hassle to make it work, since it can be either a callback ref or object ref.

I propose adding a new hook; useForwardRef that handles this.

Example on how to implement it with React:

const useForwardRef = <T,>(
  ref: ForwardedRef<T>,
  initialValue: any = null
) => {
  const targetRef = useRef<T>(initialValue);

  useEffect(() => {
    if (!ref) return;

    if (typeof ref === 'function') {
      ref(targetRef.current);
    } else {
      ref.current = targetRef.current;
    }
  }, [ref]);

  return targetRef;
};

const InputField = React.forwardRef<HTMLInputElement, InputFieldProps>(
  (props, ref) => {
   const forwardedRef = useForwardRef<HTMLInputElement>(ref);
   // Cool code....
  }
 );

lejahmie avatar Jun 14 '22 12:06 lejahmie

nicee~

devntv avatar Jun 27 '22 04:06 devntv

Maybe you should rename hook to useComposeRef.

hosseinmd avatar Jul 11 '22 10:07 hosseinmd

I'm actually not convinced why such snippet doesn't show up in the Material UI source code. But thank you very much @lejahmie , very useful piece.

NOTE: maybe material ui chopped it into pieces, https://github.com/mui/material-ui/blob/002ab79b77d4d88add840e21c5101137a4a7de5a/packages/mui-utils/src/setRef.ts . I don't really know.

windmaomao avatar Sep 16 '22 17:09 windmaomao

Maybe you should rename hook to useComposeRef.

Noa, this is more like the useCopyRef, or useCloneRef, basically it's doing a clone in the React syntax.

windmaomao avatar Sep 16 '22 17:09 windmaomao

EDIT: the original example is correct, I made a mistake wiring the old ref :)


Nice proposal @lejahmie

I copied your example into my code and it almost work, for anyone having problem with it, here is updated version:

const useForwardRef = <T,>(
  ref: ForwardedRef<T>,
  initialValue: any = null
) => {
  const targetRef = useRef<T>(initialValue);

  useEffect(() => {
    if (!ref) return;

    if (typeof ref === 'function') {
      ref(targetRef.current);
    } else {
      targetRef.current = ref.current;
    }
  }, [ref]);

  return targetRef;
};

const InputField = React.forwardRef<HTMLInputElement, InputFieldProps>(
  (props, ref) => {
   const forwardedRef = useForwardRef<HTMLInputElement>(ref);
   // Cool code....
  }
 );

Basically I updated ref.current = targetRef.current with targetRef.current = ref.current. I do not know if this is only for my specific case, but it helped me so it might helped someone else.

klis87 avatar Sep 19 '22 10:09 klis87

Basically I updated ref.current = targetRef.current with targetRef.current = ref.current. I do not know if this is only for my specific case, but it helped me so it might helped someone else.

I don't think the change is correct @klis87 , you most likely didn't wire the ref returned from this hook to the element. You are not supposed to wire the old ref any more.

windmaomao avatar Sep 19 '22 12:09 windmaomao

@windmaomao

You are not supposed to wire the old ref any more

Yes, this was the thing I did, nice guess!

klis87 avatar Sep 19 '22 21:09 klis87

sorry I have dumb question. I don't quite get how this proposed hook will work with forwardRef. With const forwardedRef = useForwardRef<HTMLInputElement>(ref);, when forwardedRef.current is set to a new value, how can ref.current be synced with the latest value? The effect won't be triggered because ref never changes.

zerofirework avatar May 08 '23 21:05 zerofirework

You're right @zerofirework, if the component has conditional logic for rendering then the forwarded ref will not be synced.

The only fix for this hook is to remove dependencies from useEffect which will force it to run on every render.
This shouldn't impact performance in any meaningful way.


For anyone having trouble with this I have a simpler proposal:

Just use a ref callback that assigns the value to both the local and the forwarded ref:

<div ref={shareRef(localRef, forwardedRef)}/>

Here's the implementation for that function

ZoranRavic avatar Jun 15 '23 22:06 ZoranRavic