From 12d230b6448a679953cb5656b897cf8e5a336ddc Mon Sep 17 00:00:00 2001 From: Ferit Date: Thu, 8 Oct 2026 04:49:21 +0300 Subject: [PATCH] fix(useRafCallback): preserve reentrant frame cancellation --- src/useRafCallback/index.dom.test.ts | 33 ++++++++++++++++++++++++++++ src/useRafCallback/index.ts | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/useRafCallback/index.dom.test.ts b/src/useRafCallback/index.dom.test.ts index 5fb345ad6..a580272da 100644 --- a/src/useRafCallback/index.dom.test.ts +++ b/src/useRafCallback/index.dom.test.ts @@ -119,4 +119,37 @@ describe('useRafCallback', () => { expect(spy).not.toHaveBeenCalled(); }); + it('should cancel a frame scheduled from inside the callback', async () => { + const spy = vi.fn(() => { + if (spy.mock.calls.length === 1) { + value[0](); + } + }); + const {result} = await renderHook(() => useRafCallback(spy)); + const value = expectResultValue(result); + + value[0](); + vi.advanceTimersToNextTimer(); + expect(spy).toHaveBeenCalledTimes(1); + + value[1](); + vi.runAllTimers(); + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('should cancel a reentrant frame on unmount', async () => { + const spy = vi.fn(() => { + if (spy.mock.calls.length === 1) { + value[0](); + } + }); + const {result, unmount} = await renderHook(() => useRafCallback(spy)); + const value = expectResultValue(result); + + value[0](); + vi.advanceTimersToNextTimer(); + await unmount(); + vi.runAllTimers(); + expect(spy).toHaveBeenCalledTimes(1); + }); }); diff --git a/src/useRafCallback/index.ts b/src/useRafCallback/index.ts index dfbacbcb0..7d9df8754 100644 --- a/src/useRafCallback/index.ts +++ b/src/useRafCallback/index.ts @@ -40,8 +40,8 @@ export function useRafCallback any>( cancel(); frame.current = requestAnimationFrame(() => { - cbRef.current(...args); frame.current = 0; + cbRef.current(...args); }); };