-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
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
[tooltip] Preventing from unnecessarily rerendering the children component #41144
Comments
Assignment moved to Michal per https://www.notion.so/mui-org/component-Tooltip-4764ddf0984c438f8b089decbe868e2e |
The Tooltip is being reimplemented in Base UI. |
@samhuk just curious if this is actually causing a performance issue for you, or if it's just something technical to note in theory? For performance, React has a built-in optimization for the <Tooltip title="Delete">
<CheapComponent>
<ExpensiveComponent />
</CheapComponent>
</Tooltip> You can ensure the direct child is a cheap node, while expensive children are either passed externally like in this example, or memo'd in the |
Thank you for the response folks.
I have done some testing and verified that the child node renders 2-3 times. When there are many on the page, it's noticable (when I just shim out Regarding the That being said, this is not high priority, so I am open to this being closed, particularly since it is being reimplemented according to @michaldudak. I don't want to create extra work 🙏 |
I understand that it's rendering multiple times, it's just that this shouldn't be a problem. Instead of shimming out the entire This issue is still relevant since the re-implementation has the same "extra" re-renders (in order to set the anchor element state, along with props, and even more when enabling grouping and transitions). However, I haven't had any issues filed about these extra re-renders being a problem. In my experience, slow renders are a problem, not extra re-renders. |
For context, one of the projects I am on is a typical mid-enterpise-size web app, where there is Some of the pages end up with ~300-500 Percentage-wise, I've managed to estimate it at around a 5-10% increase in page load times for a page without a chunky tooltip-for-each-cell table, and 20-30% otherwise. It really is dependant on the app and the engineering habits of the project.
Doesn't this require accepting either the assumption that all
To be honest, I'm surprised that nobody noticed the double or triple render of their components that are wrapped with Moving forward To me, I see two possible courses of action:
|
I've also reached this issue for performance problem. The main issue was caused by We used to hamburger menu icon for show menu list, but some how related components are more and more the button reflect really slow. <Tooltip
title="menu"
>
<IconButton onClick={handleHambugerMenuOpen} size="large">
{hamburgerMenuOpened ? (
<HamburgerMenuCloseIcon />
) : (
<HamburgerMenuIcon />
)}
</IconButton>
</Tooltip> We test it to wrap export const Tooltip = React.memo(function Tooltip({
isArrow = false,
sx = [],
children,
...rest
}: TooltipProps & { isArrow?: boolean }) {
const isTouchDevice = useMediaQuery('(pointer: coarse)');
if (isTouchDevice) {
return children;
}
return (
<MuiTooltip
arrow={isArrow}
componentsProps={{
tooltip: {
... The performance issue is seem to more revealed in mobile(touch) devices. p.s. We'd got issue when get 60~80 tooltips in same page. |
@michaldudak @atomiks It's not just a theory. We also have issues in our production project. Without the tooltips, the table renders in about 200-300ms (it's an admin table, and 200-300ms is OK). With tooltips enabled, it takes 2.7 seconds to render the same page, and this lag becomes noticeable, even for an admin page 😲 We render about 100 items, and each item has between 1 and 15 tooltips. Oh, and I'm using a MacBook Pro with Apple M2 Max - it's probably even slower on lower spec devices. |
Hi, wanted to share that we're not using the material Tooltip but instead have a similar issue with a custom tooltip implementation using @floating-ui. For example, we hit performance issues in a split-pane view with a slider, where the tooltips add noticeable lag. One workaround we implemented was to memoize the tooltip content and conditionally skip re-renders when updates are unnecessary. Here's a simplified version of the approach we used: import React from 'react'
export const MemoContent = React.memo<{ children: React.ReactNode; rerender: boolean }>(
({ children }) => <>{children}</>,
(prev, next) => {
return prev.rerender !== next.rerender ? false : !next.rerender // Skip re-render when rerender is false
},
)
export const PopoverEnabledContext = React.createContext<boolean>(true)
export const Tooltip = React.forwardRef<HTMLElement, TooltipProps>(function Tooltip(props, ref) {
const enabled = React.useContext(PopoverEnabledContext)
return (
<MemoContent rerender={enabled}>
<TooltipImpl {...props} ref={ref} />
</MemoContent>
)
}) and in split pane component, <PopoverEnabledContext.Provider value={!isSliding}>
{children}
<PopoverEnabledContext.Provider/> |
Summary
This RFC concerns the React
Tooltip
component and potential performance issues.Problem
The React
Tooltip
component unnecessarily rerenders the providedchildren
component ~2-3 times on initialTooltip
render and every time the tooltip's visibility state toggles.Cause
There are a considerable number of code paths leading up to the final
cloneElement
call here that create a large number of new references. This leads to the observed unnecessary rerendering behavior.Experimentation
I have tested this and confirmed that
Tooltip
indeed causes thechildren
component to unnecessarily render at least 2-3 additional times.As a PoC, I have also tested with a reduced-complexity (but-otherwise like-for-like) Tooltip component that proves that leveraging React memoization utils eliminates the problem. For example, the ultimate
cloneElement
call ends up looking something like this:I do not however possess the resources to delve any deeper into this (e.g. in-depth perf benchmarks). Apologies.
Open Questions
children
renders outweigh the performance loss of memoization?What are the requirements?
Zero unnecessary rerenders of the provided
children
component.What are our options?
With my current understanding, there is only one accepted way to achieve this - refactor
Tooltip
to use React memoization utils to preventchildrenProps
from being a new reference or containing new references.Proposed solution
The crux of the solution is to extract out the
cloneElement
call and place it inside auseMemo
call with the properties ofchildrenProps
as the memoization dependencies.This may require some additional uses of
useCallback
in order to ensure the event handler functions such ashandleEnter
andonMouseEnter
are not new references.Resources and benchmarks
N/A
Search keywords: tooltip, react, performance, memoization
The text was updated successfully, but these errors were encountered: