Skip to content
This repository was archived by the owner on Nov 4, 2025. It is now read-only.

Commit 5623813

Browse files
authored
fix: forceAlign should not use outdated onAlign callback (#86)
* fix: forceAlign should not use outdated onAlign callback (#85) * test: provide test case for #85
1 parent c27f11d commit 5623813

File tree

2 files changed

+43
-3
lines changed

2 files changed

+43
-3
lines changed

src/Align.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,11 @@ const Align: React.RefForwardingComponent<RefAlign, AlignProps> = (
6464
forceAlignPropsRef.current.onAlign = onAlign;
6565

6666
const [forceAlign, cancelForceAlign] = useBuffer(() => {
67-
const { disabled: latestDisabled, target: latestTarget } = forceAlignPropsRef.current;
67+
const {
68+
disabled: latestDisabled,
69+
target: latestTarget,
70+
onAlign: latestOnAlign,
71+
} = forceAlignPropsRef.current;
6872
if (!latestDisabled && latestTarget) {
6973
const source = nodeRef.current;
7074

@@ -88,8 +92,8 @@ const Align: React.RefForwardingComponent<RefAlign, AlignProps> = (
8892

8993
restoreFocus(activeElement, source);
9094

91-
if (onAlign && result) {
92-
onAlign(source, result);
95+
if (latestOnAlign && result) {
96+
latestOnAlign(source, result);
9397
}
9498

9599
return true;

tests/element.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,41 @@ describe('element align', () => {
8787
jest.runAllTimers();
8888
expect(onAlign).toHaveBeenCalled();
8989
});
90+
91+
it('should switch to the correct align callback after starting the timers', () => {
92+
// This test case is tricky. An error occurs if the following things happen
93+
// exactly in this order:
94+
// * Render <Align> with `onAlign1`.
95+
// * The callback in useBuffer is queued using setTimeout, to trigger after
96+
// `monitorBufferTime` ms (which even when it's set to 0 is queued and
97+
// not synchronously executed).
98+
// * The onAlign prop is changed to `onAlign2`.
99+
// * The callback from useBuffer is called. The now correct onAlign
100+
// callback would be `onAlign2`, and `onAlign1` should not be called.
101+
// This changing of the prop in between a 0 ms timeout is extremely rare.
102+
// It does however occur more often in real-world applications with
103+
// react-component/trigger, when its requestAnimationFrame and this timeout
104+
// race against each other.
105+
106+
const onAlign1 = jest.fn();
107+
const onAlign2 = jest.fn();
108+
109+
const wrapper = mount(<Test onAlign={onAlign1} />);
110+
111+
// Make sure the initial render's call to onAlign does not matter.
112+
onAlign1.mockReset();
113+
onAlign2.mockReset();
114+
115+
// Re-render the component with the new callback. Expect from here on all
116+
// callbacks to call the new onAlign2.
117+
wrapper.setProps({ onAlign: onAlign2 });
118+
119+
// Now the timeout is executed, and we expect the onAlign2 callback to
120+
// receive the call, not onAlign1.
121+
jest.runAllTimers();
122+
123+
expect(onAlign1).not.toHaveBeenCalled();
124+
expect(onAlign2).toHaveBeenCalled();
125+
});
90126
});
91127
/* eslint-enable */

0 commit comments

Comments
 (0)