Skip to content

Add useCombinedRefs hook, deprecate useRefObjectAsForwardedRef#7645

Draft
Copilot wants to merge 2 commits intocreate-use-combined-refsfrom
copilot/sub-pr-7638
Draft

Add useCombinedRefs hook, deprecate useRefObjectAsForwardedRef#7645
Copilot wants to merge 2 commits intocreate-use-combined-refsfrom
copilot/sub-pr-7638

Conversation

Copy link
Contributor

Copilot AI commented Mar 9, 2026

useRefObjectAsForwardedRef had a bug where useImperativeHandle would re-call on every render, risking infinite loops when combined with state-setting callback refs. This PR replaces it with a useCombinedRefs hook that uses a callback ref as its trigger, so refs are only updated when the target actually changes.

Key improvements over useRefObjectAsForwardedRef

  • No infinite loop risk: callback-ref-based trigger avoids re-firing on every render
  • React 19 compatible: handles cleanup functions returned by callback refs; remains React 18 compatible
  • Accepts undefined: works ergonomically with optional React 19 ref props
  • Order-agnostic: no silent failure if argument order is swapped
  • Supports two callback refs: internal callback refs can coexist with external refs
  • Better types: reduces need for casting

Usage

// React 18
const Example = forwardRef<HTMLButtonElement>((props, forwardedRef) => {
  const ref = useRef<HTMLButtonElement>(null)
  const combinedRef = useCombinedRefs(forwardedRef, ref)
  return <button ref={combinedRef} />
})

// React 19
const Example = ({ref: externalRef}: {ref?: Ref<HTMLButtonElement>}) => {
  const ref = useRef<HTMLButtonElement>(null)
  const combinedRef = useCombinedRefs(externalRef, ref)
  return <button ref={combinedRef} />
}

For more than two refs: useCombinedRefs(refA, useCombinedRefs(refB, refC))

Migration from useRefObjectAsForwardedRef

  const ref = useRef(null)

- useRefObjectAsForwardedRef(forwardedRef, ref)
+ const combinedRef = useCombinedRefs(forwardedRef, ref)

- return <div ref={ref} />
+ return <div ref={combinedRef} />

useRefObjectAsForwardedRef is deprecated (not removed) to allow a gradual migration; it will be deleted in the next major release.

Changelog

New

  • useCombinedRefs hook

Changed

  • useRefObjectAsForwardedRef deprecated with inline migration instructions
  • Migrated ButtonBase, Dialog, Heading, Link, Overlay, PageLayout, AutocompleteInput, AutocompleteOverlay, TextInputWithTokens, and deprecated/Dialog to useCombinedRefs

Removed

Rollout strategy

  • Patch release
  • Minor release
  • Major release; if selected, include a written rollout or migration plan
  • None; if selected, include a brief description as to why

Testing & Reviewing

Unit tests cover: forwarding to object refs, callback refs, React 19 cleanup callbacks, null/undefined handling, and combined callback+object ref scenarios.

Merge checklist


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@changeset-bot
Copy link

changeset-bot bot commented Mar 9, 2026

🦋 Changeset detected

Latest commit: 770f45d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@primer/react Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Co-authored-by: iansan5653 <2294248+iansan5653@users.noreply.github.com>
Copilot AI changed the title [WIP] Create new useCombinedRefs hook to replace useRefObjectAsForwardedRef Add useCombinedRefs hook, deprecate useRefObjectAsForwardedRef Mar 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants