# useMergedRef

> Combines any number of callback and object refs into one, with React 19 ref cleanup for each of them.

Docs: https://hextaui.com/docs/use-merged-ref
Markdown: https://hextaui.com/docs/use-merged-ref.md

```tsx title="components/examples/use-merged-ref/demo.tsx"
"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>
  )
}
```

## Installation

### CLI

```bash
npx shadcn@latest add https://hextaui.com/r/use-merged-ref.json
```

This adds the hook and anything it depends on.

### Manual

Copy and paste the following code into your project.

```ts title="hooks/use-merged-ref.ts"
import * as React from "react"

function assignRef<T>(ref: React.Ref<T> | undefined, node: T | null) {
  if (typeof ref === "function") {
    const cleanup = ref(node)
    return typeof cleanup === "function" ? cleanup : () => ref(null)
  }
  if (ref) {
    ref.current = node
    return () => {
      ref.current = null
    }
  }
  return undefined
}

function useMergedRef<T>(...refs: Array<React.Ref<T> | undefined>) {
  return React.useCallback((node: T | null) => {
    const cleanups = refs.map((ref) => assignRef(ref, node))
    return () => {
      for (const cleanup of cleanups) {
        cleanup?.()
      }
    }
  }, refs)
}

export { useMergedRef }
```

Update the import paths to match your project setup.

## Usage

```tsx
import { useMergedRef } from "@/hooks/use-merged-ref"
```

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

An element has one `ref`, but components often need to hand it to several places: the parent that forwarded a ref, a local ref for effects, and hooks that work through a callback ref.`useMergedRef` returns one callback that feeds all of them.

## How it works

When the element attaches, every ref receives it. Object refs get `.current` set, and callback refs are called with the node. The merged callback returns a cleanup in the React 19 style. On detach it runs each callback ref's own cleanup, or calls the ref with `null` if it didn't return one, and resets object refs to `null`.

```tsx
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)
```

That makes callback refs a good home for anything tied to the element's lifetime, like observers and listeners. Setup and teardown live together, and merging them with other refs keeps them working.

## Good to know

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

const logRef = React.useCallback((node) => console.log(node), [])
useMergedRef(ref, logRef)
```

- The merged callback changes whenever any ref changes. An inline arrow is a new ref on every render, so React detaches and reattaches the element each time. Wrap callback refs in `useCallback`.
- `undefined` and `null` refs are skipped, so you can pass optional props straight in.
- For one forwarded ref plus a local object ref, [useComposedRef](https://hextaui.com/docs/use-composed-ref) is shorter.

## API reference

### useMergedRef(...refs)

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `...refs` | `Array<Ref<T> \| undefined>` | – | Object refs, callback refs or undefined. |

| Returns | Description |
| --- | --- |
| `(node: T \| null) => () => void` | Pass to the element's ref. Returns the combined cleanup. |

### Used by

`Field` and `InputGroup`.

## Notes for AI assistants

- Install a component with the shadcn CLI: `npx shadcn@latest add https://hextaui.com/r/<name>.json`. It adds the source, the HextaUI theme tokens and any HextaUI components it depends on. `https://hextaui.com/r/all.json` installs every component.
- The code is then owned by the project, like shadcn/ui. There is no HextaUI npm package. HextaUI is MIT licensed and free for personal and commercial use.
- Behavior and accessibility come from Base UI (`@base-ui/react`). Compose with the `render` prop, not `asChild`.
- Styling uses Tailwind CSS v4 with theme tokens. Merge classes with `cn` from the `cn` package.
- Icons come from `@tabler/icons-react`.
- Import components from `@/components/ui/<name>`, hooks from `@/hooks/<name>` and utilities from `@/lib/<name>`.

Every HextaUI doc: https://hextaui.com/llms.txt
