Skip to content

Improve the useCombinedRefs hook by using a Proxy - #2197

Closed
iansan5653 wants to merge 3 commits into
mainfrom
fix-use-combined-refs
Closed

Improve the useCombinedRefs hook by using a Proxy#2197
iansan5653 wants to merge 3 commits into
mainfrom
fix-use-combined-refs

Conversation

@iansan5653

@iansan5653iansan5653 commented Jul 27, 2022

Copy link
Copy Markdown
Contributor

Note: This was part of #2182, but I decided to pull it out into a separate PR to make reviewing easier.

The useCombinedRefs hook is intended to provide a single ref that cascades down to several other refs. This is useful when, for example, you have a forwarded ref (that could be an object or a function) and you want to also have a local ref available as well:

constExample=forwardRef(({},forwardedRef)=>{constcombinedRef=useCombinedRefs(forwardedRef)return<divref={combinedRef}/>}

When the combinedRef is updated, the forwardedRef is updated as well. There can even be multiple forwardedRefs that will all get updated.

The current implementation uses useLayoutEffect to synchronize all the refs on every render. This typically works well, but can have edge cases because refs can update without causing a re-render. This is particularly likely if the ref is being used to hold a value that is not an element or if the ref refers to some element that is a deep child (children could update without re-rendering the parent).

This PR changes the implementation to use a Proxy as the target ref instead, so that any time the target ref is updated, all dependent refs are updated immediately. No layout effect is required using this strategy.

We could have used a function as the target ref instead - this would have made change detection easy without use of a Proxy. But there are many use cases where the consumer also wants to get the current value of the ref using .current and that's not available with a function.

This change brings useCombinedRefs up to date with the latest code used in production in Projects. I also added the hook to hooks/index so that we can update that codebase to use the PRC version of the hook and remove some duplication.

@iansan5653
iansan5653 requested review from a team and rezrahJuly 27, 2022 14:42
@changeset-bot

changeset-botBot commented Jul 27, 2022

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: f91f682

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@iansan5653iansan5653 changed the title Improve the useCombinedRefs hookImprove the useCombinedRefs hook by using a ProxyJul 27, 2022
@github-actions

github-actionsBot commented Jul 27, 2022

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
dist/browser.esm.js68.63 KB (+0.08% 🔺)
dist/browser.umd.js69 KB (+0.07% 🔺)

@iansan5653
iansan5653 temporarily deployed to github-pages July 27, 2022 14:52 Inactive
@siddharthkpsiddharthkp added react 💓collab a vibrant hub of collaboration labels Jul 27, 2022

@LukeghencoLukeghenco left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me (as someone testing the component that uses this right now on the PRs React team), but I don't have real permissions to approve here.

@colebemiscolebemis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me 👍

It is possible to unit test this hook ensure that it works as we expect? Also, could you add a patch changeset to this PR?

@iansan5653

Copy link
Copy Markdown
ContributorAuthor

@colebemis I can definitely add unit tests. How would I add a changeset?

@colebemis

Copy link
Copy Markdown
Contributor

@iansan5653 You can click this link:
CleanShot 2022-08-01 at 09 00 30

That will create a markdown file for you where you can describe your changes.

@mattcosta7

mattcosta7 commented Aug 1, 2022

Copy link
Copy Markdown
Contributor

Curious about whether we can basically remove this hook entirely (or just wrap the native react implemetation of it)

useImperativeHandle(ref,()=>internalRef.current)// used likeforwardRef((props,forwardedRef)=>{constref=useRef()useImperativeHandle(forwardedRef,()=>ref.current)return<divref={ref}/>})

This forwads the 'internal' ref to the potentially forwardedRef already - and is the native react impl of this (which means it's already included in the react bundle)

What an imperative handle does

https://github.com/facebook/react/blob/49f8254d6dec7c6de789116e20ffc5a9401aa725/packages/react-reconciler/src/ReactFiberHooks.old.js#L1769-L1860

While combined refs allows passing many refs, where a hook might be a little less ergonomic since it won't handle being called in a loop well, it gets some of this out of this library

we could use the callback to provide multiple refs together, which should limit the extra code we run for this if we need to

@iansan5653

Copy link
Copy Markdown
ContributorAuthor

Curious about whether we can basically remove this hook entirely (or just wrap the native react implemetation of it)

Hmm, that actually might work. I'll bet that 9/10 or more uses of this hook would be resolved by that example - I think it's very rare to actually combine more than one ref.

@iansan5653

Copy link
Copy Markdown
ContributorAuthor

Closing this in favor of #2204 - thanks @mattcosta7!

@joshblack
joshblack deleted the fix-use-combined-refs branch January 19, 2023 16:48
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💓collaba vibrant hub of collaborationreact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@iansan5653@colebemis@mattcosta7@Lukeghenco@siddharthkp