From 83a15611b62867f503552cefaad688e19d5ce092 Mon Sep 17 00:00:00 2001 From: Jakub Magiera Date: Fri, 21 Aug 2026 16:35:38 +0200 Subject: [PATCH 1/2] reset execution state on unmount for Activity mode changes --- src/hooks/useSingleExecution/index.native.ts | 3 ++ .../hooks/useSingleExecutionTest.native.ts | 34 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/hooks/useSingleExecution/index.native.ts b/src/hooks/useSingleExecution/index.native.ts index 2cd703367408..7c64f4f7f81b 100644 --- a/src/hooks/useSingleExecution/index.native.ts +++ b/src/hooks/useSingleExecution/index.native.ts @@ -18,6 +18,9 @@ export default function useSingleExecution() { useEffect( () => () => { transitionHandleRef.current?.cancel(); + transitionHandleRef.current = null; + isExecutingRef.current = false; + setIsExecuting(false); }, [], ); diff --git a/tests/unit/hooks/useSingleExecutionTest.native.ts b/tests/unit/hooks/useSingleExecutionTest.native.ts index 540dc3805052..82464d889f76 100644 --- a/tests/unit/hooks/useSingleExecutionTest.native.ts +++ b/tests/unit/hooks/useSingleExecutionTest.native.ts @@ -2,6 +2,9 @@ import {act, renderHook} from '@testing-library/react-native'; import TransitionTracker from '@libs/Navigation/TransitionTracker'; +import {Activity, createElement} from 'react'; +import type {PropsWithChildren} from 'react'; + type NavigationListener = (event: {data: Record}) => void; type UseSingleExecution = () => { isExecuting: boolean; @@ -99,6 +102,37 @@ describe('useSingleExecution (native)', () => { expect(action).toHaveBeenCalledTimes(1); }); + it('resets execution when Activity hides the hook before the unlock callback runs', () => { + const action = jest.fn(); + let activityMode: 'visible' | 'hidden' = 'visible'; + const wrapper = ({children}: PropsWithChildren) => { + const activityProps = {mode: activityMode, children}; + return createElement(Activity, activityProps); + }; + const {result, rerender} = renderHook(() => useSingleExecution(), {wrapper}); + + act(() => { + result.current.singleExecution(action)(); + }); + + expect(result.current.isExecuting).toBe(true); + + activityMode = 'hidden'; + rerender({}); + + activityMode = 'visible'; + rerender({}); + act(() => { + result.current.singleExecution(action)(); + }); + + expect(action).toHaveBeenCalledTimes(2); + + act(() => { + jest.runOnlyPendingTimers(); + }); + }); + it('cancels the pending transition handle on unmount', () => { const cancelSpy = jest.fn(); const runAfterTransitionsSpy = jest.spyOn(TransitionTracker, 'runAfterTransitions').mockReturnValue({cancel: cancelSpy}); From 764e8c096bf1637bd3924303f5f303c76fb486bc Mon Sep 17 00:00:00 2001 From: Jakub Magiera Date: Fri, 21 Aug 2026 19:29:49 +0200 Subject: [PATCH 2/2] run fmt --- tests/unit/hooks/useSingleExecutionTest.native.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/hooks/useSingleExecutionTest.native.ts b/tests/unit/hooks/useSingleExecutionTest.native.ts index 82464d889f76..7a4ba59a26aa 100644 --- a/tests/unit/hooks/useSingleExecutionTest.native.ts +++ b/tests/unit/hooks/useSingleExecutionTest.native.ts @@ -2,9 +2,10 @@ import {act, renderHook} from '@testing-library/react-native'; import TransitionTracker from '@libs/Navigation/TransitionTracker'; -import {Activity, createElement} from 'react'; import type {PropsWithChildren} from 'react'; +import {Activity, createElement} from 'react'; + type NavigationListener = (event: {data: Record}) => void; type UseSingleExecution = () => { isExecuting: boolean;