HextaUI

useComposedRef

Behält eine Ref auf dein eigenes Element und leitet sie trotzdem an die Ref weiter, die der Parent übergeben hat.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { useComposedRef } from "@/hooks/use-composed-ref"

function CharacterInput({ ref, ...props }: React.ComponentProps<"input">) {
  const [inputRef, setRef] = useComposedRef<HTMLInputElement>(ref)
  const [length, setLength] = React.useState(0)

  return (
    <div className="flex h-9 w-full items-center gap-2 rounded-md bg-muted pe-3 focus-within:ring-3 focus-within:ring-focus-ring">
      <input
        ref={setRef}
        {...props}
        onChange={(event) => {
          props.onChange?.(event)
          setLength(event.target.value.length)
        }}
        className="h-full min-w-0 flex-1 bg-transparent ps-3 text-sm outline-none focus-visible:outline-hidden pointer-coarse:text-touch"
      />
      <button
        type="button"
        hidden={length === 0}
        onClick={() => {
          const input = inputRef.current
          if (input) {
            input.value = ""
            setLength(0)
            input.focus()
          }
        }}
        className="text-xs text-muted-foreground hover:text-foreground"
      >
        Clear
      </button>
    </div>
  )
}

export function UseComposedRefDemo() {
  const ref = React.useRef<HTMLInputElement>(null)

  return (
    <div className="flex w-full max-w-xs flex-col gap-3">
      <CharacterInput ref={ref} aria-label="Search" placeholder="Search" />
      <Button variant="outline" size="sm" onClick={() => ref.current?.focus()}>
        Focus from the parent
      </Button>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/use-composed-ref.json

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

import { useComposedRef } from "@/hooks/use-composed-ref"
function SearchInput({ ref, ...props }: React.ComponentProps<"input">) {
  const [inputRef, setRef] = useComposedRef<HTMLInputElement>(ref)

  React.useEffect(() => {
    inputRef.current?.select()
  }, [inputRef])

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

In React 19 ist ref eine normale Prop. Braucht deine Komponente das Element auch für sich selbst, hast du zwei Refs und nur ein ref-Attribut. Dieser Hook gibt dir eine Objekt-Ref zum Lesen sowie einen Callback, der beide Refs füllt.

function SearchInput({ ref, ...props }: React.ComponentProps<"input">) {
  const inputRef = React.useRef<HTMLInputElement>(null)
  return <input ref={inputRef} {...props} />
}

Das kompiliert, aber die ref des Elternelements geht verloren, sodass ref.current im Elternelement null bleibt. Mit useComposedRef erhalten sowohl das Elternelement als auch die Komponente das Element.

  • Die zurückgegebene inputRef ist eine normale RefObject, sodass du sie an Hooks übergeben kannst, die eine erwarten, etwa useAutosize oder useInvalidShake.
  • setRef behält dieselbe Identität, solange die Ref des Elternelements sie behält, sodass React sie nicht bei jedem Render löst und neu anhängt.
  • Funktioniert mit Objekt-Refs, Callback-Refs und ganz ohne Ref.

Nutze useComposedRef, wenn du genau eine weitergereichte Ref mit deiner eigenen Objekt-Ref kombinierst, was bei den meisten Komponenten der Fall ist. Nutze useMergedRef, wenn du mehr als zwei kombinierst, wenn eine davon eine Callback-Ref aus einem anderen Hook ist oder wenn eine weitergereichte Callback-Ref eine React-19-Cleanup-Funktion zurückgibt. useComposedRef ruft eine Callback-Ref beim Lösen mit null auf, statt ihre Cleanup-Funktion auszuführen.

PropTypStandard
refDie Ref, die deine Komponente erhalten hat.
Ref<T> | undefined–
RückgabeBeschreibung
[0] RefObject<T | null>Deine eigene Ref auf das Element.
[1] (node: T | null) => voidAn die Ref des Elements übergeben. Füllt beide Refs.

Input, Textarea, NativeSelect, Field und InputGroup.