You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Perf] Tooltip: every move re-renders the React chart, builds unused default content and rewrites unchanged attributes #187
With a custom tooltip body (and with the motion() renderer for F), each pointer move that changes the tooltip does work no one sees: the React adapter re-renders the chart (A), core builds the default content the custom body replaces (E), and paint rewrites attributes that did not change (F). Related: #179 (only the default tooltip duration).
Paths are under packages/; links pin commit 57774e1.
A. Tooltip target state re-renders the chart (react-charts)
useTooltipBody keeps the target in React.useState; onChange calls setTarget (tooltip.tsx:152-159).
Repro (vitest + jsdom, 4 pointer moves): expected 3 to be +0 calls to adapter.update.
Expected: a target change updates only the tooltip body. Proposed: keep the target in an external store read by a leaf component (useSyncExternalStore).
Moved to #210 (a feature, together with the per-phase motion proposed under B).
E. Default content is always built
paint always calls createChartTooltipContent before the custom body (tooltip.ts:117-124), and the result feeds the aria-label (:293).
Repro: an items[].text formatter is called 1 time per paint with a custom body, expected 0.
Expected: default content computed only when read. Proposed: resolve content lazily, or let a custom body supply the accessible label.
Downstream measurement (bklit migration, Chrome CDP profile, 300 moves): about 6 ms per 100 moves; grows with series count.
F. Each paint rewrites the tooltip element (from this comment)
On every move that repaints the tooltip, paint sets visibility: hidden and removes it again, and re-sets className, pointer-events, user-select and data-sticky, even when nothing changed (tooltip.ts:106-162). configureTooltipSemantics re-sets role, aria-live and aria-label.
The spring glide writes style.translate from requestAnimationFrame every frame (motion.ts:419-428, motion.ts:484-497). The tween glide uses WAAPI instead.
Measured (production build, sankey, MutationObserver on the tooltip, 60 moves): about 12 mutation records per move. About 4.7 are translate, 2.5 visibility, 3.75 the unchanged attributes, 1.8 content and 1 left/top.
Proposed: compare before writing in paint. Run the spring glide as a sampled WAAPI translate, like the tween path.
Checked against 57774e14a1a86081eddb3a23523724a40da05640 (v1.1.0).
On every move that repaints the tooltip, paint sets visibility: hidden and removes it again, and re-sets className, pointer-events, user-select and data-sticky, even when nothing changed (tooltip.ts:106-162). configureTooltipSemantics re-sets role, aria-live and aria-label.
The spring glide writes style.translate from requestAnimationFrame every frame (motion.ts:419-428, motion.ts:484-497). The tween glide uses WAAPI instead.
Measured (production build, sankey, MutationObserver on the tooltip, 60 moves): about 12 mutation records per move. About 4.7 are translate, 2.5 visibility, 3.75 the unchanged attributes, 1.8 content and 1 left/top.
Proposal: compare before writing in paint. Run the spring glide as a sampled WAAPI translate, like the tween path.
changed the title [-]Tooltip lifecycle: chart re-renders on tooltip change, repeated hide() restarts the exit, no first-show control[/-][+][Perf] Tooltip: every move re-renders the React chart, builds unused default content and rewrites unchanged attributes[/+]on Oct 13, 2026
Thanks @toomingos. Part A is fixed in #234, which has merged but is not on npm yet. Custom tooltip target state now stays in the tooltip child instead of rerendering the React chart host. The regression tests fail on the old implementation with three extra adapter updates, and the fix passed ordinary and StrictMode tests plus packed-package lifecycle checks on React 18.0, 18.3 and 19.2 in Chromium, Firefox and WebKit. The scene renderer already avoided redraws before this fix, so this is specifically a React host update improvement. Keeping this issue open for the remaining content construction and DOM-write work.
Activity
toomingos commentedon Oct 12, 2026
F. Each paint rewrites the tooltip element
Checked against
57774e14a1a86081eddb3a23523724a40da05640(v1.1.0).paintsetsvisibility: hiddenand removes it again, and re-setsclassName,pointer-events,user-selectanddata-sticky, even when nothing changed (tooltip.ts:106-162).configureTooltipSemanticsre-setsrole,aria-liveandaria-label.style.translatefromrequestAnimationFrameevery frame (motion.ts:419-428, motion.ts:484-497). The tween glide uses WAAPI instead.Measured (production build, sankey,
MutationObserveron the tooltip, 60 moves): about 12 mutation records per move. About 4.7 aretranslate, 2.5visibility, 3.75 the unchanged attributes, 1.8 content and 1left/top.Proposal: compare before writing in
paint. Run the spring glide as a sampled WAAPItranslate, like the tween path.[-]Tooltip lifecycle: chart re-renders on tooltip change, repeated hide() restarts the exit, no first-show control[/-][+][Perf] Tooltip: every move re-renders the React chart, builds unused default content and rewrites unchanged attributes[/+]tannerlinsley commentedon Oct 16, 2026
Thanks @toomingos. Part A is fixed in #234, which has merged but is not on npm yet. Custom tooltip target state now stays in the tooltip child instead of rerendering the React chart host. The regression tests fail on the old implementation with three extra adapter updates, and the fix passed ordinary and StrictMode tests plus packed-package lifecycle checks on React 18.0, 18.3 and 19.2 in Chromium, Firefox and WebKit. The scene renderer already avoided redraws before this fix, so this is specifically a React host update improvement. Keeping this issue open for the remaining content construction and DOM-write work.