Skip to content

Commit 47ec1aa

Browse files
authored
fix: avoid stale FloatingMenu position updates (#22284)
1 parent 55d8f6c commit 47ec1aa

2 files changed

Lines changed: 72 additions & 17 deletions

File tree

packages/react/src/internal/FloatingMenu.tsx

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -314,13 +314,17 @@ export const FloatingMenu = ({
314314
});
315315

316316
// Only update if the position has actually changed.
317-
if (
318-
!floatingPosition ||
319-
floatingPosition.left !== newFloatingPosition.left ||
320-
floatingPosition.top !== newFloatingPosition.top
321-
) {
322-
setFloatingPosition(newFloatingPosition);
323-
}
317+
setFloatingPosition((prev) => {
318+
if (
319+
prev &&
320+
prev.left === newFloatingPosition.left &&
321+
prev.top === newFloatingPosition.top
322+
) {
323+
return prev;
324+
}
325+
326+
return newFloatingPosition;
327+
});
324328

325329
// Re-check after setting the position if not already adjusting.
326330
if (!isAdjustment) {
@@ -334,15 +338,7 @@ export const FloatingMenu = ({
334338
}
335339
}
336340
},
337-
[
338-
triggerRef,
339-
menuOffset,
340-
menuDirection,
341-
flipped,
342-
target,
343-
updateOrientation,
344-
floatingPosition,
345-
]
341+
[triggerRef, menuOffset, menuDirection, flipped, target, updateOrientation]
346342
);
347343

348344
const focusMenuContent = (menuBody: HTMLElement) => {

packages/react/src/internal/__tests__/FloatingMenu-test.js

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import React from 'react';
8+
import React, { Profiler } from 'react';
99
import {
1010
act,
1111
fireEvent,
@@ -389,4 +389,63 @@ describe('FloatingMenu', () => {
389389
expect(menu.style.top).toBe('60px');
390390
});
391391
});
392+
393+
it('should not commit again when repeated resizes keep the same updated menu position', async () => {
394+
const remove = jest.fn();
395+
let resizeCallback;
396+
const onRender = jest.fn();
397+
const trigger = document.createElement('button');
398+
const triggerRef = { current: trigger };
399+
const target = () => document.body;
400+
401+
trigger.setAttribute('data-testid', 'trigger');
402+
document.body.appendChild(trigger);
403+
404+
jest.spyOn(OptimizedResize, 'add').mockImplementation((callback) => {
405+
resizeCallback = callback;
406+
return { remove };
407+
});
408+
409+
render(
410+
<Profiler id="floating-menu" onRender={onRender}>
411+
<FloatingMenu
412+
triggerRef={triggerRef}
413+
menuOffset={defaultMenuOffset}
414+
target={target}>
415+
{defaultMenuChildren}
416+
</FloatingMenu>
417+
</Profiler>
418+
);
419+
420+
await waitFor(() => {
421+
expect(screen.getByTestId('menu').style.left).toBe('-23px');
422+
expect(screen.getByTestId('menu').style.top).toBe('65px');
423+
});
424+
425+
triggerRect = createRect({
426+
left: 50,
427+
top: 20,
428+
right: 70,
429+
bottom: 60,
430+
width: 20,
431+
height: 40,
432+
});
433+
434+
act(() => {
435+
resizeCallback();
436+
});
437+
438+
await waitFor(() => {
439+
expect(screen.getByTestId('menu').style.left).toBe('17px');
440+
expect(screen.getByTestId('menu').style.top).toBe('65px');
441+
});
442+
443+
const commitCountAfterPositionChange = onRender.mock.calls.length;
444+
445+
act(() => {
446+
resizeCallback();
447+
});
448+
449+
expect(onRender).toHaveBeenCalledTimes(commitCountAfterPositionChange);
450+
});
392451
});

0 commit comments

Comments
 (0)