diff --git a/packages/mui-material/src/Tooltip/Tooltip.js b/packages/mui-material/src/Tooltip/Tooltip.js
index f0ffb0c969f66a..2ad80427481a08 100644
--- a/packages/mui-material/src/Tooltip/Tooltip.js
+++ b/packages/mui-material/src/Tooltip/Tooltip.js
@@ -280,6 +280,10 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) {
});
let open = openState;
+ const openRef = React.useRef(open);
+ React.useLayoutEffect(() => {
+ openRef.current = open;
+ });
if (process.env.NODE_ENV !== 'production') {
// TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler
@@ -329,9 +333,11 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) {
// The mouseover event will trigger for every nested element in the tooltip.
// We can skip rerendering when the tooltip is already open.
// We are using the mouseover event instead of the mouseenter event to fix a hide/show issue.
+ const wasOpen = openRef.current;
+ openRef.current = true;
setOpenState(true);
- if (onOpen && !open) {
+ if (onOpen && !wasOpen) {
onOpen(event);
}
};
@@ -341,12 +347,14 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) {
* @param {React.SyntheticEvent | Event} event
*/
(event) => {
+ const wasOpen = openRef.current;
hystersisTimer.start(800 + leaveDelay, () => {
hystersisOpen = false;
});
setOpenState(false);
+ openRef.current = false;
- if (onClose && open) {
+ if (onClose && wasOpen) {
onClose(event);
}
diff --git a/packages/mui-material/src/Tooltip/Tooltip.test.js b/packages/mui-material/src/Tooltip/Tooltip.test.js
index 3106a6578c43fb..3eb9e615271578 100644
--- a/packages/mui-material/src/Tooltip/Tooltip.test.js
+++ b/packages/mui-material/src/Tooltip/Tooltip.test.js
@@ -451,6 +451,38 @@ describe('', () => {
expect(eventLog).to.deep.equal(['mouseover', 'open', 'mouseleave', 'close']);
});
+ it('should call onClose when controlled close follows a delayed open before rerender', () => {
+ const eventLog = [];
+ render(
+ eventLog.push('open')}
+ onClose={() => eventLog.push('close')}
+ open={false}
+ >
+
+ ,
+ );
+
+ fireEvent.mouseOver(screen.getByRole('button'));
+ clock.tick(100);
+
+ expect(eventLog).to.deep.equal(['mouseover', 'open']);
+
+ fireEvent.mouseLeave(screen.getByRole('button'));
+ clock.tick(0);
+
+ expect(eventLog).to.deep.equal(['mouseover', 'open', 'mouseleave', 'close']);
+ });
+
it('should not call onOpen again if already open', () => {
const eventLog = [];
render(