HextaUI

useMergedRef

Combina cualquier cantidad de refs de callback y de objeto en una sola, con la limpieza de refs de React 19 para cada una.

0px
"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { useMergedRef } from "@/hooks/use-merged-ref"

function MeasuredBox({
  ref,
  onWidth,
  ...props
}: React.ComponentProps<"div"> & { onWidth: (width: number) => void }) {
  const measureRef = React.useCallback(
    (node: HTMLDivElement | null) => {
      if (!node) {
        return
      }
      const observer = new ResizeObserver(([entry]) =>
        onWidth(Math.round(entry.contentRect.width))
      )
      observer.observe(node)
      return () => observer.disconnect()
    },
    [onWidth]
  )
  const setRef = useMergedRef(ref, measureRef)

  return <div ref={setRef} {...props} />
}

export function UseMergedRefDemo() {
  const ref = React.useRef<HTMLDivElement>(null)
  const [width, setWidth] = React.useState(0)
  const [wide, setWide] = React.useState(false)

  return (
    <div className="flex w-full max-w-sm flex-col items-center gap-4">
      <MeasuredBox
        ref={ref}
        onWidth={setWidth}
        data-wide={wide ? "" : undefined}
        className="flex h-16 w-1/2 items-center justify-center rounded-lg bg-muted font-mono text-sm tabular-nums transition-all duration-300 ease-out-quint data-wide:w-full motion-reduce:transition-none"
      >
        {width}px
      </MeasuredBox>
      <div className="flex gap-2">
        <Button variant="outline" size="sm" onClick={() => setWide((v) => !v)}>
          Resize
        </Button>
        <Button
          variant="ghost"
          size="sm"
          onClick={() =>
            ref.current?.animate(
              [{ scale: 1 }, { scale: 0.96 }, { scale: 1 }],
              { duration: 240, easing: "ease-out" }
            )
          }
        >
          Nudge via parent ref
        </Button>
      </div>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/use-merged-ref.json

Añade el hook y todo lo que necesita a tu proyecto.

import { useMergedRef } from "@/hooks/use-merged-ref"
function Panel({ ref, ...props }: React.ComponentProps<"div">) {
  const localRef = React.useRef<HTMLDivElement>(null)
  const morphRef = useSizeMorph<HTMLDivElement>({ axis: "height" })
  const setRef = useMergedRef(ref, localRef, morphRef)

  return <div ref={setRef} {...props} />
}

Un elemento tiene un solo ref, pero los componentes a menudo necesitan entregarlo a varios sitios: el padre que reenvió un ref, un ref local para efectos y hooks que trabajan mediante un ref de callback. useMergedRef devuelve un solo callback que alimenta a todos.

Cuando el elemento se adjunta, cada ref lo recibe. A los refs de objeto se les asigna .current, y a los refs de callback se les llama con el nodo. El callback combinado devuelve una limpieza al estilo de React 19. Al desvincular, ejecuta la limpieza propia de cada ref de callback, o llama al ref con null si no devolvió una, y restablece los refs de objeto a null.

const observeRef = React.useCallback((node: HTMLDivElement | null) => {
  if (!node) return
  const observer = new ResizeObserver(onResize)
  observer.observe(node)
  return () => observer.disconnect()
}, [onResize])

const setRef = useMergedRef(ref, observeRef)

Eso hace de los refs de callback un buen lugar para todo lo ligado a la vida del elemento, como observers y listeners. La configuración y el desmontaje viven juntos, y combinarlos con otros refs los mantiene funcionando.

useMergedRef(ref, (node) => console.log(node))

const logRef = React.useCallback((node) => console.log(node), [])
useMergedRef(ref, logRef)
  • El callback combinado cambia siempre que cambia cualquier ref. Una función flecha en línea es un ref nuevo en cada renderizado, así que React desvincula y vuelve a vincular el elemento cada vez. Envuelve los refs de callback en useCallback.
  • Los refs undefined y null se omiten, así que puedes pasar directamente props opcionales.
  • Para un ref reenviado más un ref de objeto local, useComposedRef es más corto.
PropTipoPredeterminado
...refsRefs de objeto, refs de callback o undefined.
Array<Ref<T> | undefined>–
DevuelveDescripción
(node: T | null) => () => voidPásalo al ref del elemento. Devuelve la limpieza combinada.

Field e InputGroup.