Skip to content

[Tabs] Fix React 18 roving tabindex and dedupe invalid-value warning#48605

Merged
Janpot merged 3 commits into
mui:masterfrom
Janpot:fix/tabs-react18-roving-tabindex
Jun 1, 2026
Merged

[Tabs] Fix React 18 roving tabindex and dedupe invalid-value warning#48605
Janpot merged 3 commits into
mui:masterfrom
Janpot:fix/tabs-react18-roving-tabindex

Conversation

@Janpot
Copy link
Copy Markdown
Member

@Janpot Janpot commented Jun 1, 2026

Fixes the nightly-cron test_unit-react@18 failures in Tabs.test.js. Both reproduce only under React 18 (pass under React 19/next), and have separate causes.

1. useRovingTabIndex: active item not updating under React 18 (real bug)

<Tabs> did not move the roving tabIndex to the newly selected tab when the value prop changed under React 18 (confirmed with waitFor — it never converges, so it's a behavior bug, not test timing). React 18 is a supported peer dependency, so this affects real users.

The hook derived state from the activeItemId prop by mutating a ref during render:

const previousActiveItemIdPropRef = React.useRef(activeItemIdProp);
if (activeItemIdProp !== previousActiveItemIdPropRef.current) {
  previousActiveItemIdPropRef.current = activeItemIdProp; // mutating a ref during render
  ...
}

That isn't idempotent under StrictMode's double-render: React resets state between the two render invocations but preserves ref mutations, so on the replay the guard already sees the new value and skips the update. Switched to the documented "store the previous prop in state" pattern.

2. Tabs: invalid value warning logged from every effect

The invalid-value dev warning lives inside getTabsMeta, which runs from several effects, so it logged multiple times per mount and the count varied under React 18 StrictMode. Guarded it with a WeakMap keyed by the Tabs instance's ref, so the warning is logged once per instance (rather than once globally — a global flag would persist across instances/tests and suppress later warnings). The WeakMap is only referenced from process.env.NODE_ENV !== 'production' blocks and carries a /* @__PURE__ */ annotation, so minifiers drop it (and the allocation) from production builds. Simplified the test expectation accordingly.

Verification

useRovingTabIndex + Tabs + MenuList + Stepper + MobileStepper test suites pass under both react@18 (useReactVersion ^18) and react@next (19.3.0-canary).

@Janpot Janpot added the scope: code-infra Involves the code-infra product (https://www.notion.so/mui-org/5562c14178aa42af97bc1fa5114000cd). label Jun 1, 2026
@code-infra-dashboard
Copy link
Copy Markdown

code-infra-dashboard Bot commented Jun 1, 2026

Deploy preview

https://deploy-preview-48605--material-ui.netlify.app/

Bundle size

Bundle Parsed size Gzip size
@mui/material ▼-9B(0.00%) ▼-5B(0.00%)
@mui/lab 0B(0.00%) 0B(0.00%)
@mui/private-theming 0B(0.00%) 0B(0.00%)
@mui/system 0B(0.00%) 0B(0.00%)
@mui/utils 0B(0.00%) 0B(0.00%)

Details of bundle changes


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

@Janpot Janpot force-pushed the fix/tabs-react18-roving-tabindex branch from e3eb2cf to abcae94 Compare June 1, 2026 15:02
@Janpot Janpot force-pushed the fix/tabs-react18-roving-tabindex branch from abcae94 to 3a1ad68 Compare June 1, 2026 15:09
@Janpot Janpot marked this pull request as ready for review June 1, 2026 15:28
Comment thread packages/mui-material/src/Tabs/Tabs.test.js Outdated
Co-authored-by: Albert Yu <albert@albertyu.co>
Signed-off-by: Jan Potoms <2109932+Janpot@users.noreply.github.com>
@Janpot Janpot enabled auto-merge (squash) June 1, 2026 17:17
@Janpot Janpot merged commit 7e015de into mui:master Jun 1, 2026
18 checks passed
@Janpot Janpot deleted the fix/tabs-react18-roving-tabindex branch June 1, 2026 17:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: code-infra Involves the code-infra product (https://www.notion.so/mui-org/5562c14178aa42af97bc1fa5114000cd).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants