Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I assign multiple refs to the same Element/Node?

I have a function component that forwards the incoming ref from the parent to a div that it is rendering. I also want to create a ref inside the component and assign it to the same div. But I can't since an element takes only one ref. Am I doing something wrong or is there a workaround for an issue like this?

The ref coming from the parent is a React.Ref, but I need a React.RefObject to pass it to 3rd party hooks like react-use's clickAway: https://github.com/streamich/react-use/blob/master/docs/useClickAway.md

Here is the example component:

import React, { useRef } from 'react';

type Props = React.PropsWithoutRef<JSX.IntrinsicElements['div']>;

function Component({ ...props }: Props, ref: React.Ref<HTMLDivElement>) {
  const wrapper = useRef<HTMLDivElement>(null);

  return (
    <div
      {...props}
      ref={ref}
      // ref={wrapper}
    />
  );
}

export default React.forwardRef(Component);
like image 586
Gokhan Sari Avatar asked Oct 19 '25 22:10

Gokhan Sari


1 Answers

Short Answer


You can use a simple function like this to merge the refs
const mergeRefs = (...refs) => {
  return node => {
    for (const ref of refs) {
      ref.current = node
    }
  }
}

Why it works


The reason why this works is because, under the hood, the ref prop can accept a function with a self referencing node as the argument.
import React from "react"

export default () => {
  const ref1 = React.useRef(null)
  const ref2 = React.useRef(null)
  return (
    <div
      ref={node => {
        ref1.current = node
        ref2.current = node
      }}
    >
      Hello
    </div>
  )
}

Credit for the examples goes to this article.

useRef Typing


Typewise, it looks like this. Personally, it's not clear to me how to read `RefCallback` - it looks like a self referencing function with a generic argument T and somehow T represents the node itself. Open to someone further clarifying it.
interface RefObject<T> {
    readonly current: T | null;
}
type RefCallback<T> = { bivarianceHack(instance: T | null): void }["bivarianceHack"];
type Ref<T> = RefCallback<T> | RefObject<T> | null;

Typescript Answer


If you want to add a few more checks and balances you can use this typescript compatible function taken from headlessui from Tailwind.
function useSyncRefs<TType>(
  ...refs: (
    | React.MutableRefObject<TType | null>
    | ((instance: TType) => void)
    | null
  )[]
) {
  let cache = React.useRef(refs);

  React.useEffect(() => {
    cache.current = refs;
  }, [refs]);

  return React.useCallback(
    (value: TType) => {
      for (let ref of cache.current) {
        if (ref == null) {
          console.log('ref is null');
          continue;
        }
        if (typeof ref === 'function') {
          console.log('ref is a function. Returning called function');
          ref(value)
        } else {
          console.log('returning the value: ', value);
          ref.current = value
        };
      }
    },
    [cache]
  );
};

You can see it in action in this Expo Snack.

like image 117
wongz Avatar answered Oct 21 '25 13:10

wongz