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(