Skip to content

fix(floating-ui): prevent setState-during-commit crash in React 19 - #23128

Open
snicolas-ibm wants to merge 1 commit into
carbon-design-system:mainfrom
snicolas-ibm:fix/react-19-floating-ui-setreference-setfloating
Open

fix(floating-ui): prevent setState-during-commit crash in React 19#23128
snicolas-ibm wants to merge 1 commit into
carbon-design-system:mainfrom
snicolas-ibm:fix/react-19-floating-ui-setreference-setfloating

Conversation

@snicolas-ibm

@snicolas-ibm snicolas-ibm commented Aug 26, 2026

Copy link
Copy Markdown

Closes #

React 19 calls ref callbacks synchronously during the commit phase. The
refs.setFloating and refs.setReference functions returned by
useFloating() from @floating-ui/react are backed by useState setters.
Passing them directly as DOM ref props caused a setState-during-commit cycle,
exceeding React 19's nested update limit and crashing with
"Maximum update depth exceeded".

The fix captures the DOM node in a plain ref inside a stable useCallback
ref callback, then forwards it to the floating-ui setter inside a useEffect
(which runs after commit, safely outside the synchronous commit phase).

Changelog

Fixed

  • Fixed a "Maximum update depth exceeded" crash in React 19 affecting
    MenuButton, OverflowMenu (next), ComboButton, ComboBox,
    Dropdown, MultiSelect, FilterableMultiSelect, and MenuItem
    when @floating-ui/react ref callbacks were used directly as DOM ref props.

Testing / Reviewing

  1. Upgrade your app to React 19
  2. Render any of the 8 affected components — previously they crashed on mount
    with "Maximum update depth exceeded"; they should now render correctly
  3. Run the test suite: yarn jest --testPathPatterns "MenuButton|OverflowMenu|ComboButton|ComboBox|Dropdown|MultiSelect|MenuItem"
    — all 352 tests pass

PR Checklist

As the author of this PR, before marking ready for review, confirm you:

  • Reviewed every line of the diff
  • Updated documentation and storybook examples — no public API or visual change
  • Followed the required v12 migration documentation — this fix is not v12-specific behaviour, it is a React 19 compatibility patch with no consumer migration burden
  • Wrote passing tests that cover this change — existing tests (352) all pass; dedicated React 19 render tests are not yet added
  • Addressed any impact on accessibility (a11y) — no change
  • Tested for cross-browser consistency — no change
  • Validated that this code is ready for review and status checks should pass

@snicolas-ibm
snicolas-ibm requested a review from a team as a code owner August 26, 2026 14:17
@netlify

netlify Bot commented Aug 26, 2026

Copy link
Copy Markdown

Deploy Preview for v12-carbon-web-components ready!

Name Link
🔨 Latest commit 84daf93
🔍 Latest deploy log https://app.netlify.com/projects/v12-carbon-web-components/deploys/6a8ef56becdfce000881d96b
😎 Deploy Preview https://deploy-preview-23128--v12-carbon-web-components.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

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

@netlify

netlify Bot commented Aug 26, 2026

Copy link
Copy Markdown

Deploy Preview for v12-carbon-react ready!

Name Link
🔨 Latest commit 84daf93
🔍 Latest deploy log https://app.netlify.com/projects/v12-carbon-react/deploys/6a8ef56a89688100085744db
😎 Deploy Preview https://deploy-preview-23128--v12-carbon-react.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

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

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the DCO.
Posted by the DCO Assistant Lite bot.

@netlify

netlify Bot commented Aug 26, 2026

Copy link
Copy Markdown

Deploy Preview for v11-carbon-web-components ready!

Name Link
🔨 Latest commit 84daf93
🔍 Latest deploy log https://app.netlify.com/projects/v11-carbon-web-components/deploys/6a8ef56a907c660008e4e82c
😎 Deploy Preview https://deploy-preview-23128--v11-carbon-web-components.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

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

@netlify

netlify Bot commented Aug 26, 2026

Copy link
Copy Markdown

Deploy Preview for v11-carbon-react ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 84daf93
🔍 Latest deploy log https://app.netlify.com/projects/v11-carbon-react/deploys/6a8ef56aa57f0d000801de1f
😎 Deploy Preview https://deploy-preview-23128--v11-carbon-react.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

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

@netlify

netlify Bot commented Aug 26, 2026

Copy link
Copy Markdown

Deploy Preview for carbon-elements ready!

Name Link
🔨 Latest commit 84daf93
🔍 Latest deploy log https://app.netlify.com/projects/carbon-elements/deploys/6a8ef56aed2d720008e9307d
😎 Deploy Preview https://deploy-preview-23128--carbon-elements.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

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

@snicolas-ibm

Copy link
Copy Markdown
Author

I have read the DCO document and I hereby sign the DCO.

@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.67%. Comparing base (5d916f1) to head (84daf93).
⚠️ Report is 11 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff            @@
##             main   #23128    +/-   ##
========================================
  Coverage   89.67%   89.67%            
========================================
  Files         639      639            
  Lines       58879    59007   +128     
  Branches     8300     8286    -14     
========================================
+ Hits        52797    52917   +120     
- Misses       5916     5924     +8     
  Partials      166      166            
Flag Coverage Δ
main-packages 87.25% <100.00%> (+0.10%) ⬆️
web-components 90.52% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment on lines +442 to +446
if (pendingFloatingNodeRef.current !== null) {
const node = pendingFloatingNodeRef.current;
pendingFloatingNodeRef.current = null;
refs.setFloating(node);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The null callback from React is the detach signal, but the !== null guard drops it. When the floating element unmounts or floating styles are disabled, refs.setFloating(null) never runs, so Floating UI keeps a reference to a removed element. Should the pending null value be preserved and forwarded?

This comment applies elsewhere too.

Comment on lines +434 to +437
// React 19: refs.setFloating / refs.setReference are useState setters.
// Passing them as ref callbacks causes setState during commit → crash.
// Capture the node in a ref and forward it in a passive effect (after commit).
const pendingFloatingNodeRef = useRef(null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are all eight copies of the React 19 explanation necessary? Could the ref forwarding logic and rationale live in a shared hook, making the component call sites less repetitive?

Comment on lines +441 to +459
useEffect(() => {
if (pendingFloatingNodeRef.current !== null) {
const node = pendingFloatingNodeRef.current;
pendingFloatingNodeRef.current = null;
refs.setFloating(node);
}
});

const pendingReferenceNodeRef = useRef(null);
const setReferenceSafe = useCallback((node) => {
pendingReferenceNodeRef.current = node;
}, []);
useEffect(() => {
if (pendingReferenceNodeRef.current !== null) {
const node = pendingReferenceNodeRef.current;
pendingReferenceNodeRef.current = null;
refs.setReference(node);
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This workaround changes ref timing and detach behavior, but no tests were added. Could you add test coverage?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants