HextaUI

useMergedRef

Combine un nombre quelconque de refs callback et objet en une seule, avec le nettoyage de ref de React 19 pour chacune.

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

Ajoute le hook et tout ce dont il dépend à votre projet.

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 élément n'a qu'une seule ref, mais les composants doivent souvent la confier à plusieurs endroits : le parent qui a transmis une ref, une ref locale pour les effets, et des hooks qui passent par une ref callback. useMergedRef renvoie un seul callback qui alimente toutes ces refs.

Quand l'élément s'attache, chaque ref le reçoit. Les refs objet voient leur .current défini, et les refs callback sont appelées avec le nœud. Le callback fusionné renvoie un nettoyage à la manière de React 19. Au détachement, il exécute le nettoyage propre à chaque ref callback, ou appelle la ref avec null si elle n'en a pas renvoyé, et remet les refs objet à 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)

Les refs callback conviennent donc à tout ce qui est lié à la durée de vie de l'élément, comme les observateurs et les écouteurs. L'installation et le démontage vivent ensemble, et les fusionner avec d'autres refs les garde fonctionnels.

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

const logRef = React.useCallback((node) => console.log(node), [])
useMergedRef(ref, logRef)
  • Le callback fusionné change dès qu'une ref change. Une fonction fléchée en ligne est une nouvelle ref à chaque rendu : React détache et rattache donc l'élément à chaque fois. Enveloppez les refs callback dans useCallback.
  • Les refs undefined et null sont ignorées : vous pouvez donc passer directement des props facultatives.
  • Pour une ref transmise plus une ref objet locale, useComposedRef est plus court.
PropTypePar défaut
...refsRefs objet, refs callback ou undefined.
Array<Ref<T> | undefined>–
Valeur de retourDescription
(node: T | null) => () => voidÀ passer à la ref de l'élément. Renvoie le nettoyage combiné.

Field et InputGroup.