HextaUI

useMergedRef

Combina qualquer número de refs de callback e de objeto em uma só, com a limpeza de ref do React 19 para cada uma delas.

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

Adiciona o hook e tudo de que ele depende ao seu projeto.

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} />
}

Um elemento tem uma única ref, mas os componentes muitas vezes precisam repassá-lo a vários lugares: o pai que encaminhou uma ref, uma ref local para effects e hooks que funcionam por meio de uma ref de callback. useMergedRef retorna um único callback que alimenta todos eles.

Quando o elemento é anexado, toda ref o recebe. Refs de objeto têm .current definido, e refs de callback são chamadas com o nó. O callback mesclado retorna um cleanup no estilo do React 19. Ao desanexar, executa o cleanup de cada ref de callback, ou chama a ref com null se ela não retornou um, e redefine as refs de objeto para 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)

Isso faz das refs de callback um bom lugar para tudo o que está ligado ao ciclo de vida do elemento, como observers e listeners. Configuração e desmontagem ficam juntas, e mesclá-las com outras refs as mantém funcionando.

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

const logRef = React.useCallback((node) => console.log(node), [])
useMergedRef(ref, logRef)
  • O callback mesclado muda sempre que qualquer ref muda. Uma arrow function inline é uma nova ref a cada renderização, então o React desanexa e reanexa o elemento todas as vezes. Envolva refs de callback em useCallback.
  • Refs undefined e null são ignoradas, então você pode passar props opcionais diretamente.
  • Para uma ref repassada mais uma ref de objeto local, useComposedRef é mais curto.
PropTipoPadrão
...refsRefs de objeto, refs de callback ou undefined.
Array<Ref<T> | undefined>–
RetornaDescrição
(node: T | null) => () => voidPasse para a ref do elemento. Retorna o cleanup combinado.

Field e InputGroup.