Skip to content

Commit b07caa5

Browse files
committed
[@mantine.core] Transition clear all timers and RAF on rapid state changes to prevent animation glitches
- Add clearAllTimeouts to clear transitionTimeoutRef, delayTimeoutRef, and rafRef - Ensure all timers and RAF are cleared before starting a new transition or delay - Prevents animation glitches and state corruption when toggling rapidly
1 parent dc693f7 commit b07caa5

1 file changed

Lines changed: 9 additions & 11 deletions

File tree

packages/@mantine/core/src/components/Transition/use-transition.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,16 @@ export function useTransition({
4545
const delayTimeoutRef = useRef<number>(-1);
4646
const rafRef = useRef(-1);
4747

48+
function clearAllTimeouts() {
49+
window.clearTimeout(transitionTimeoutRef.current);
50+
window.clearTimeout(delayTimeoutRef.current);
51+
cancelAnimationFrame(rafRef.current);
52+
}
53+
4854
const handleStateChange = (shouldMount: boolean) => {
55+
clearAllTimeouts();
4956
const preHandler = shouldMount ? onEnter : onExit;
5057
const handler = shouldMount ? onEntered : onExited;
51-
52-
window.clearTimeout(transitionTimeoutRef.current);
53-
5458
const newTransitionDuration = reduceMotion ? 0 : shouldMount ? duration : exitDuration;
5559
setTransitionDuration(newTransitionDuration);
5660

@@ -59,16 +63,13 @@ export function useTransition({
5963
typeof handler === 'function' && handler();
6064
setStatus(shouldMount ? 'entered' : 'exited');
6165
} else {
62-
// Make sure new status won't be set within the same frame as this would disrupt animation #3126
6366
rafRef.current = requestAnimationFrame(() => {
6467
ReactDOM.flushSync(() => {
6568
setStatus(shouldMount ? 'pre-entering' : 'pre-exiting');
6669
});
67-
6870
rafRef.current = requestAnimationFrame(() => {
6971
typeof preHandler === 'function' && preHandler();
7072
setStatus(shouldMount ? 'entering' : 'exiting');
71-
7273
transitionTimeoutRef.current = window.setTimeout(() => {
7374
typeof handler === 'function' && handler();
7475
setStatus(shouldMount ? 'entered' : 'exited');
@@ -79,14 +80,12 @@ export function useTransition({
7980
};
8081

8182
const handleTransitionWithDelay = (shouldMount: boolean) => {
82-
window.clearTimeout(delayTimeoutRef.current);
83+
clearAllTimeouts();
8384
const delay = shouldMount ? enterDelay : exitDelay;
84-
8585
if (typeof delay !== 'number') {
8686
handleStateChange(shouldMount);
8787
return;
8888
}
89-
9089
delayTimeoutRef.current = window.setTimeout(
9190
() => {
9291
handleStateChange(shouldMount);
@@ -101,8 +100,7 @@ export function useTransition({
101100

102101
useEffect(
103102
() => () => {
104-
window.clearTimeout(transitionTimeoutRef.current);
105-
cancelAnimationFrame(rafRef.current);
103+
clearAllTimeouts();
106104
},
107105
[]
108106
);

0 commit comments

Comments
 (0)