Back to Code Bytes
3 min read
useMergeRefs

Description

Merges multiple refs into a single ref. This is useful when building UI components that have a local ref, but also support external refs via React.forwardRef.

Code Byte

import React from 'react';

export function mergeRefs<T>(
  refs: Array<React.Ref<T> | undefined>
): React.RefCallback<T> | null {
  if (refs.every((ref) => ref == null)) {
    return null;
  }

  return (value) => {
    refs.forEach((ref) => {
      if (typeof ref === 'function') {
        ref(value);
      } else if (ref != null) {
        (ref as React.MutableRefObject<T | null>).current = value;
      }
    });
  };
}

/**
 * @description Merges multiple refs into a single ref.
 * This is useful when building UI components that have a local ref,
 * but also support external refs via `React.forwardRef`.
 *
 * @example
 * const Component = React.forwardRef((props, ref) => {
 *  const localRef = React.useRef();
 *  const mergedRef = useMergeRefs([localRef, ref]);
 *
 *  return <div ref={mergedRef} {...props} />;
 *  });
 * */
export function useMergeRefs<T>(
  refs: Array<React.Ref<T> | undefined>
): React.RefCallback<T> | null {
  return React.useMemo(() => {
    return mergeRefs(refs);
  }, refs);
}

Arguments

ArgumentsTypeDefaultRequiredDescription
dispatchArray<React.Ref<T> | undefined>-✅A list of refs to merge.

Usage

const Component = React.forwardRef((props, ref) => {
  const localRef = React.useRef();
  const mergedRef = useMergeRefs([localRef, ref]);

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

Tests

import { renderHook } from '@testing-library/react-hooks';
import { useMergeRefs } from './useMergeRefs.hook';

describe('useMergeRefs()', () => {
  it('should merge multiple refs into a single ref', () => {
    const ref1 = { current: null };
    const ref2 = { current: null };
    const ref3 = { current: null };

    const { result } = renderHook(() =>
      useMergeRefs<string>([ref1, ref2, ref3])
    );

    const mergedRef = result.current;

    mergedRef?.('test');

    expect(ref1.current).toBe('test');
    expect(ref2.current).toBe('test');
    expect(ref3.current).toBe('test');
  });

  it('should return null if all refs are null', () => {
    const { result } = renderHook(() => useMergeRefs([null, null, null]));

    expect(result.current).toBeNull();
  });

  it('should return null if all refs are undefined', () => {
    const { result } = renderHook(() =>
      useMergeRefs([undefined, undefined, undefined])
    );

    expect(result.current).toBeNull();
  });
});