HextaUI

useMergedRef

Kombiniert beliebig viele Callback- und Objekt-Refs zu einem, mit React-19-Ref-Cleanup für jeden von ihnen.

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

Fügt den Hook und alles, wovon er abhängt, zu deinem Projekt hinzu.

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

Ein Element hat eine ref, aber Komponenten müssen es oft an mehrere Stellen weitergeben: das Elternelement, das eine Ref weitergereicht hat, eine lokale Ref für Effects und Hooks, die über eine Callback-Ref arbeiten. useMergedRef gibt einen Callback zurück, der alle speist.

Wird das Element angehängt, erhält jede Ref es. Objekt-Refs bekommen .current gesetzt, und Callback-Refs werden mit dem Node aufgerufen. Der gemergte Callback gibt eine Cleanup-Funktion im Stil von React 19 zurück. Beim Lösen führt er die eigene Cleanup-Funktion jeder Callback-Ref aus oder ruft die Ref mit null auf, falls sie keine zurückgab, und setzt Objekt-Refs auf null zurück.

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)

Damit sind Callback-Refs ein guter Ort für alles, was an die Lebensdauer des Elements gebunden ist, wie Observer und Listener. Setup und Teardown liegen zusammen, und das Mergen mit anderen Refs hält sie funktionsfähig.

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

const logRef = React.useCallback((node) => console.log(node), [])
useMergedRef(ref, logRef)
  • Der gemergte Callback ändert sich, sobald sich eine Ref ändert. Eine Inline-Arrow-Funktion ist bei jedem Render eine neue Ref, sodass React das Element jedes Mal löst und neu anhängt. Umschließe Callback-Refs mit useCallback.
  • Refs, die undefined oder null sind, werden übersprungen, sodass du optionale Props direkt übergeben kannst.
  • Für eine weitergereichte Ref plus eine lokale Objekt-Ref ist useComposedRef kürzer.
PropTypStandard
...refsObjekt-Refs, Callback-Refs oder undefined.
Array<Ref<T> | undefined>–
RückgabeBeschreibung
(node: T | null) => () => voidAn die Ref des Elements übergeben. Gibt die kombinierte Cleanup-Funktion zurück.

Field und InputGroup.