HextaUI

useComposedRef

Keeps a ref to your own element while still forwarding it to whatever ref the parent passed in.

"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

Adds the hook and anything it depends on to your project.

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, ref is a regular prop. When your component also needs the element for itself, you have two refs and only one ref attribute. This hook gives you an object ref to read, plus a callback that fills both refs.

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

This compiles, but the parent's ref is dropped, so ref.current stays null in the parent. With useComposedRef, both the parent and the component get the element.

  • The returned inputRef is a normal RefObject, so you can pass it to hooks that expect one, like useAutosize or useInvalidShake.
  • setRef keeps the same identity while the parent's ref does, so React doesn't detach and reattach it on every render.
  • Works with object refs, callback refs and no ref at all.

Use useComposedRef when you combine exactly one forwarded ref with your own object ref, which is most components. Use useMergedRef when you combine more than two, when one of them is a callback ref from another hook, or when a forwarded callback ref returns a React 19 cleanup function. useComposedRef calls a callback ref with null on detach instead of running its cleanup.

PropTypeDefault
refThe ref your component received.
Ref<T> | undefined–
ReturnsDescription
[0] RefObject<T | null>Your own ref to the element.
[1] (node: T | null) => voidPass to the element's ref. Fills both refs.

Input, Textarea, NativeSelect, Field and InputGroup.