Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the useCombinedRefs hook by using a Proxy #2197

Closed
wants to merge 3 commits into from

Conversation

iansan5653
Copy link
Contributor

@iansan5653 iansan5653 commented Jul 27, 2022

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:

const Example = forwardRef(({}, forwardedRef) => {
  const combinedRef = useCombinedRefs(forwardedRef)

  return <div ref={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 rezrah July 27, 2022 14:42
@changeset-bot
Copy link

changeset-bot bot commented Jul 27, 2022

⚠️ 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

@iansan5653 iansan5653 changed the title Improve the useCombinedRefs hook Improve the useCombinedRefs hook by using a Proxy Jul 27, 2022
@github-actions
Copy link
Contributor

github-actions bot commented Jul 27, 2022

size-limit report 📦

Path Size
dist/browser.esm.js 68.63 KB (+0.08% 🔺)
dist/browser.umd.js 69 KB (+0.07% 🔺)

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

@Lukeghenco Lukeghenco left a comment

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.

Copy link
Contributor

@colebemis colebemis left a comment

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
Contributor Author

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

@colebemis
Copy link
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
Copy link
Collaborator

mattcosta7 commented Aug 1, 2022

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

useImperativeHandle(ref, () => internalRef.current)


// used like
forwardRef((props, forwardedRef) => {
   const ref = useRef()
   useImperativeHandle(forwardedRef, () => ref.current)
   
   return <div ref={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
Contributor Author

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
Contributor Author

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

@iansan5653 iansan5653 closed this Aug 1, 2022
@joshblack joshblack deleted the fix-use-combined-refs branch January 19, 2023 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
💓collab a vibrant hub of collaboration react
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants