Skip to content

[internal] Clean up floating-ui-react hooks#4550

Open
atomiks wants to merge 6 commits intomui:masterfrom
atomiks:codex/clean-up-floating-ui-react-hooks
Open

[internal] Clean up floating-ui-react hooks#4550
atomiks wants to merge 6 commits intomui:masterfrom
atomiks:codex/clean-up-floating-ui-react-hooks

Conversation

@atomiks
Copy link
Copy Markdown
Contributor

@atomiks atomiks commented Apr 7, 2026

This cleans up the internal floating-ui-react hooks so the shared interaction code is easier to scan and maintain without changing behavior.

Changes

  • Normalize hook ordering, grouping, and nearby derived values across all the hooks
  • Simplify duplicated control flow in the click, dismiss, focus, hover, list navigation, typeahead, role, client point, and floating hooks.
  • Remove deprecated code

@atomiks atomiks added internal Behind-the-scenes enhancement. Formerly called “core”. scope: all components Widespread work has an impact on almost all components. labels Apr 7, 2026 — with ChatGPT Codex Connector
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 7, 2026

commit: 96c04b1

@mui-bot
Copy link
Copy Markdown

mui-bot commented Apr 7, 2026

Bundle size report

Bundle Parsed size Gzip size
@base-ui/react ▼-1.03KB(-0.23%) ▼-19B(-0.01%)

Details of bundle changes


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

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 7, 2026

Deploy Preview for base-ui ready!

Name Link
🔨 Latest commit b812c1a
🔍 Latest deploy log https://app.netlify.com/projects/base-ui/deploys/69d5142bc5c2e40008a090f5
😎 Deploy Preview https://deploy-preview-4550--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
Copy link
Copy Markdown

netlify bot commented Apr 7, 2026

Deploy Preview for base-ui ready!

Name Link
🔨 Latest commit 96c04b1
🔍 Latest deploy log https://app.netlify.com/projects/base-ui/deploys/69d6060b41145b0008b0f83c
😎 Deploy Preview https://deploy-preview-4550--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.

@atomiks atomiks marked this pull request as ready for review April 7, 2026 15:10
@atomiks atomiks changed the title [all components] Clean up floating-ui-react hooks [internal] Clean up floating-ui-react hooks Apr 8, 2026
@atomiks atomiks force-pushed the codex/clean-up-floating-ui-react-hooks branch from 02b5375 to eb3eafa Compare April 8, 2026 07:12
Copy link
Copy Markdown
Member

@flaviendelangle flaviendelangle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: #4550 — [internal] Clean up floating-ui-react hooks

Branch: codex/clean-up-floating-ui-react-hooksmaster
Stats: +420 / -466 across 14 files
Scope: packages/react/src/floating-ui-react/


Critical Issues (0 found)

None.


Important Issues (1 found)

1. useTypeahead: Removing setTypingChange loses deduplication guard

File: packages/react/src/floating-ui-react/hooks/useTypeahead.ts

The old setTypingChange wrapper used dataRef.current.typing to deduplicate calls — it only fired onTypingChange when the typing state actually transitioned (false→true or true→false). The PR removes both the guard and the typing field from ContextData, replacing all calls with direct onTypingChange?.(value).

This means onTypingChange?.(true) can now be called multiple times in succession during a single typing session (e.g., when typing two characters quickly, the intermediate mismatch triggers onTypingChange(false) then onTypingChange(true) again, whereas the old code would suppress the redundant true call).

Current consumers (MenuRoot, SelectRoot) just do typingRef.current = nextTyping, so the practical impact is minimal (idempotent ref assignment). However, this is a semantic change that could affect future consumers expecting transition-only callbacks.

Suggestion: Either keep a local boolean ref to preserve the dedup behavior:

const typingRef = React.useRef(false);
// Then wrap calls:
if (value !== typingRef.current) {
  typingRef.current = value;
  onTypingChange?.(value);
}

Or explicitly document that onTypingChange may now fire redundantly and add a test covering multi-key typeahead to confirm this is acceptable.


Suggestions (3 found)

1. useClick: getNextOpen has an unclear boolean expression

The extracted getNextOpen helper preserves the original logic but the nested negation with ternary is hard to follow:

return (
  (open && hasClickedOnInactiveTrigger) ||
  !(open && toggle && (openEvent && stickIfOpen ? isClickLikeOpenEvent(openEvent.type) : true))
);

Since it's now a named function, this was a good opportunity to use explicit branching:

if (open && hasClickedOnInactiveTrigger) return true;
if (!open) return true;
if (!toggle) return true;
if (stickIfOpen && openEvent) return !isClickLikeOpenEvent(openEvent.type);
return false;

2. useHover: isClickLikeOpenEvent could use the shared helper

In useHover.ts, isClickLikeOpenEvent manually checks ['click', 'mousedown'].includes(...), while useHoverReferenceInteraction and useHoverFloatingInteraction already use the shared isClickLikeOpenEventShared from useHoverShared.ts. Aligning useHover to also use the shared helper would reduce duplication.

3. useTypeahead: reference/floating aliases are unnecessary

After the PR, the code creates sharedProps then assigns it to both reference and floating. The intermediate variables are trivial aliases — the return could directly use sharedProps:

return React.useMemo(
  () => (enabled ? { reference: sharedProps, floating: sharedProps } : {}),
  [enabled, sharedProps],
);

Strengths

  • Consistent hook ordering — props destructuring before store/context access is now uniform across all hooks, making them easier to scan.
  • Good deduplicationsetOpenWithTouchDelay, getNextOpen, resetBlockedFocus, hasBlockingChild, isEventWithinOwnElements, openOnNavigationKeyDown all reduce repeated inline logic.
  • Correct dependency management — moving closeWithDelay, handleInteractInside, and similar helpers into effect bodies is safe because the captured values (delayRef, store, timeout, instance) are all identity-stable refs/stores. No stale closure risk.
  • Type safety improvementcloseOnReferencePress in useDismiss replaces as any with an explicit MouseEvent | PointerEvent | TouchEvent | KeyboardEvent union.
  • getParentOrientation converted to useStableCallback — follows the project's CLAUDE.md guidelines since it's called within an event handler.
  • Safe removal of disabledIndicesRef from useListNavigation depsuseValueAsRef returns an identity-stable ref; only .current changes.
  • Import reordering — alphabetical organization improves consistency.
  • Deprecated typing field removal from ContextData — only read/written within useTypeahead.ts itself; no external consumers.

Recommended Action

  1. Decide on the setTypingChange dedup behavior — either restore deduplication with a local ref or confirm the change is intentional.
  2. Consider the three suggestions above as optional polish.
  3. Existing tests should be run to confirm no regressions (pnpm test:jsdom --no-watch and pnpm test:chromium --no-watch for affected components).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

internal Behind-the-scenes enhancement. Formerly called “core”. scope: all components Widespread work has an impact on almost all components.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants