diff --git a/packages/mui-material/package.json b/packages/mui-material/package.json
index 3692d62f304084..0716eb5e553135 100644
--- a/packages/mui-material/package.json
+++ b/packages/mui-material/package.json
@@ -91,6 +91,9 @@
"engines": {
"node": ">=14.0.0"
},
+ "browser": {
+ "react-transition-group/cjs/TransitionGroupContext.js": "react-transition-group/esm/TransitionGroupContext.js"
+ },
"exports": {
".": "./src/index.js",
"./ButtonBase/TouchRipple": "./src/ButtonBase/TouchRipple.js",
diff --git a/packages/mui-material/src/internal/Transition.test.tsx b/packages/mui-material/src/internal/Transition.test.tsx
index f792e5fc2c58af..087cef5b8da907 100644
--- a/packages/mui-material/src/internal/Transition.test.tsx
+++ b/packages/mui-material/src/internal/Transition.test.tsx
@@ -2,6 +2,7 @@
import * as React from 'react';
import { expect } from 'chai';
import { spy } from 'sinon';
+import { TransitionGroup } from 'react-transition-group';
import TransitionGroupContext from 'react-transition-group/TransitionGroupContext';
import { act, createRenderer, screen } from '@mui/internal-test-utils';
import Transition from './Transition';
@@ -990,6 +991,53 @@ describe('', () => {
});
});
+ describe('react-transition-group public TransitionGroup interop', () => {
+ it('child added to an already-mounted TransitionGroup enters with isAppearing=false', async () => {
+ const handlers = { onEnter: spy(), onEntered: spy() };
+ let done: (() => void) | null = null;
+ const addEndListener = (_node: HTMLElement, next: () => void) => {
+ done = next;
+ };
+
+ function ChildWrapper() {
+ const [shouldRender, setShouldRender] = React.useState(false);
+ return (
+
+
+
+ {shouldRender ? (
+
+ ) : null}
+
+
+ );
+ }
+
+ const { user } = render();
+ await user.click(screen.getByRole('button', { name: 'add' }));
+
+ expect(screen.getByTestId('target')).to.have.attribute('data-status', 'entering');
+ expect(handlers.onEnter.callCount).to.equal(1);
+ expect(handlers.onEnter.args[0][0]).to.equal(false);
+
+ act(() => {
+ done!();
+ });
+
+ expect(screen.getByTestId('target')).to.have.attribute('data-status', 'entered');
+ expect(handlers.onEntered.callCount).to.equal(1);
+ expect(handlers.onEntered.args[0][0]).to.equal(false);
+ });
+ });
+
describe('react-transition-group TransitionGroupContext user interactions', () => {
const mountedGroup = { isMounting: false };
diff --git a/packages/mui-material/src/internal/Transition.tsx b/packages/mui-material/src/internal/Transition.tsx
index bde3801241b60e..0b57c0c76f6e80 100644
--- a/packages/mui-material/src/internal/Transition.tsx
+++ b/packages/mui-material/src/internal/Transition.tsx
@@ -6,7 +6,10 @@ import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
import useValueAsRef from '@mui/utils/useValueAsRef';
// Material UI transitions must still work inside react-transition-group's TransitionGroup.
// Import only its context module; do not import its Transition or TransitionGroup components.
-import TransitionGroupContext from 'react-transition-group/TransitionGroupContext';
+// Use RTG's explicit CJS file for Node ESM/SSR; package.json's `browser` field redirects
+// browser bundles to RTG's ESM file.
+// eslint-disable-next-line import/extensions -- Node ESM needs the explicit .js extension.
+import TransitionGroupContext from 'react-transition-group/cjs/TransitionGroupContext.js';
import { reflow } from '../transitions/utils';
type RenderedTransitionStatus = 'entering' | 'entered' | 'exiting' | 'exited';
diff --git a/packages/mui-material/src/internal/react-transition-group.d.ts b/packages/mui-material/src/internal/react-transition-group.d.ts
index ef20f1c36ce0cd..bca21db721ead7 100644
--- a/packages/mui-material/src/internal/react-transition-group.d.ts
+++ b/packages/mui-material/src/internal/react-transition-group.d.ts
@@ -8,3 +8,14 @@ declare module 'react-transition-group/TransitionGroupContext' {
const TransitionGroupContext: React.Context;
export default TransitionGroupContext;
}
+
+declare module 'react-transition-group/cjs/TransitionGroupContext.js' {
+ import * as React from 'react';
+
+ interface TransitionGroupContextValue {
+ isMounting: boolean;
+ }
+
+ const TransitionGroupContext: React.Context;
+ export default TransitionGroupContext;
+}