base-ui icon indicating copy to clipboard operation
base-ui copied to clipboard

[popups] Improve performance when detached triggers are used

Open michaldudak opened this issue 1 month ago • 4 comments

Refactored the FloatingRootContext to be based on the Store, allowing for selective subscription to changes in its state.

Previously, FloatingRootContext was recreated whenever the open state changed. As triggers were subscribed to it, they all rerendered whenever an associated popup opened or closed.

This improvement applies to all "detachable" components: Menu, Tooltip, Popover, and (Alert)Dialog.

Additionally, in the Menu, due to unnecessary subscriptions, triggers also rerendered when the active item index within a menu was updated (so hovering over menu items caused re-rendering of all related menu triggers).

Before (Menu example):

https://github.com/user-attachments/assets/c25b37a2-c102-482b-9641-3f3ab7d1938b

After:

https://github.com/user-attachments/assets/97b0eb0f-6c5b-4cf2-af7f-109c7e3421d6


Technical notes

The biggest change is the rewrite of FloatingRootContext. It now acts as a backing data store that useFloating uses. Instead of being recreated on every render or dependency change, it keeps a stable reference for the lifetime of a component. A store implementation allows hooks to selectively depend on pieces of state. Also, thanks to Store's select method, interaction hooks can use non-reactive values in event handlers and effects.

The FloatingRootStore is created based on the component store with the new hook: UseSyncedFloatingRootContext. It makes sure the pieces of state in these two stores are kept in sync (see also point 1 in Next steps below).

To simplify types, I removed the type parameter from useFloating (previously RT extends ReferenceType). We never made use of it, so I used ReferenceType directly where RT was used before.

I moved most of the logic out of triggers, reducing the initial number of renders to 2 (the minimum possible with composable API). I also unified their implementation, extracting common parts.

Next steps

  1. We can further improve the implementation by deduplicating pieces of state such as open, floatingElement, etc. from both stores (main component store and FloatingRootStore). We can consider making the Floating UI primitives to work with our PopupStore (or the other way around - use FloatingRootStore to keep component data).

  2. Make triggers lazy - skip all effects until the rendered element is interacted with. This requires further investigation.

michaldudak avatar Nov 20 '25 11:11 michaldudak

vite-css-base-ui-example

pnpm add https://pkg.pr.new/mui/base-ui/@base-ui-components/react@3277
pnpm add https://pkg.pr.new/mui/base-ui/@base-ui-components/utils@3277

commit: 8ede505

pkg-pr-new[bot] avatar Nov 20 '25 12:11 pkg-pr-new[bot]

Bundle size report

Bundle Parsed size Gzip size
@base-ui-components/react 🔺+1.27KB(+0.31%) 🔺+314B(+0.24%)

Details of bundle changes


Check out the code infra dashboard for more information about this PR.

mui-bot avatar Nov 20 '25 12:11 mui-bot

Deploy Preview for base-ui ready!

Name Link
Latest commit 8ede505ab884d3784b2008f9d027e7e35547f75f
Latest deploy log https://app.netlify.com/projects/base-ui/deploys/692847083b92140008d5be97
Deploy Preview https://deploy-preview-3277--base-ui.netlify.app
Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

netlify[bot] avatar Nov 20 '25 12:11 netlify[bot]

  • The [data-popup-open] state (on both Menu/Tooltip) seems to be incorrectly linked to mounted now instead of open so it lingers as popups close
  • The [data-ending-style] transition of an instant tooltip issue returned (#2962)

atomiks avatar Nov 26 '25 02:11 atomiks

Bugs:

  • Tooltip: Pressing a trigger with the pointer before the tooltip shows no longer cancels it from showing
  • Combobox: Pressing a trigger to toggle the popup (closed once it's open) no longer works when the input is outside the popup

Let's add regression tests for the issues encountered here as well

atomiks avatar Nov 27 '25 04:11 atomiks

If I'm not mistaken, this seems to have introduced a regression: https://github.com/mui/base-ui/issues/3363

OliverJAsh avatar Nov 28 '25 11:11 OliverJAsh