-
-
Notifications
You must be signed in to change notification settings - Fork 300
[internal] Refactor useHover hooks as classes
#3451
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
base: master
Are you sure you want to change the base?
Conversation
commit: |
Bundle size report
Check out the code infra dashboard for more information about this PR. |
✅ Deploy Preview for base-ui ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
packages/react/src/floating-ui-react/hooks/useHoverInteractionSharedState.ts
Outdated
Show resolved
Hide resolved
|
I don't see a difference with master running the tooltip benchmark here |
|
This might be missing commits from master or my baseline might not have been up-to-date, but I'll wait before resuming the work here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where do performance gains come from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useStableCallback runs an effect on every render to make sure the function you call is the one created in the last render (and which has access to up-to-date variables around it).
This goes away with a store class that is initialized once on mount.
Same with React.useCallback, on every render React has to compare the old and new deps.
From what I understand, it's death by a thousand cuts. A lot of very cheap operations on a hook used in lots of components accross the page.
I would not expect similar gains when applying the same logic on a bigger component (but I still think the code is more readable on the Tree View post migration).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(maybe I'm missing other perf gains in this PR ofc)
For example, I think having lots of useRef in a component can have a small cost that adds up when compared to a class field. But I did not check React implementation of this hook.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cherniavskii you can have a look at #3444 for some additional context
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the rule of thumb "extract everything stable into a class" correct?
And for effects or callbacks with dependencies, keep them in the hook (the one that would initialize the class), or in the Hook.use method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Performancewise, maybe it's the optimal solution
I would keep internal DX in mind if this comes at the cost of our productivity 👀
But I suggest that we let the Base UI team continue the exploration for some time.
Then see where we would have the biggest gain on X (mostly for the new version of the components, I don't suggest to spend time on something that would not benefit the new versions) and start from there.
I don't think it's worth converting a hook / component that has 2 React.useCallback and handles a big amount of data to a class, because the gain will be marginal (the React.useCallback is not the bulk of the work).
But I honestly don't have a clear vision of where this pattern should be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I understand, it's death by a thousand cuts.
This is it.
For example, I think having lots of useRef in a component can have a small cost that adds up when compared to a class field. But I did not check React implementation of this hook.
For sure, instead of having on single big object in a contiguous memory region, you have many small objects spread in memory, so the cache locality is bad. React compounds the issue by using a linked list instead of an arrya to store hook state.
const hookObject = {
fields: {
value: 1,
next: {
value: 2,
next: {
value: 3,
next: null, } } }
}
const classObject = {
fieldA: 1,
fieldB: 2,
fieldC: 3,
}
But I suggest that we let the Base UI team continue the exploration for some time.
Yes, there are ergonomy problems with mixing hooks with classes due to typescript limitations, and we still need to think about sync'ing props at the right time in the render pipeline to avoid hard to debug issues. I want to test this out slowly with a few hooks.
Counterpart of #3391
Refactor the
useHoverhooks into classes.Results