diff --git a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts index 5708870405..403a37b487 100644 --- a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts +++ b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts @@ -77,6 +77,7 @@ let Reanimated: runOnUI( fn: (...args: A) => R ): (...args: Parameters) => void; + makeMutable(value: T): { value: T }; } | undefined; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/eventHandler.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/eventHandler.ts index 82480ffe68..f1c36f5202 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/eventHandler.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/eventHandler.ts @@ -3,7 +3,6 @@ import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture'; import type { ReanimatedContext } from '../../../handlers/gestures/reanimatedWrapper'; import { State } from '../../../State'; import { TouchEventType } from '../../../TouchEventType'; -import { tagMessage } from '../../../utils'; import type { ChangeCalculatorType, GestureCallbacks, @@ -93,16 +92,11 @@ export function handleUpdateEvent< : eventWithData; const event = flattenAndFilterEvent(eventWithChanges); - - // This should never happen, but since we don't want to call hooks conditionally, we have to mark - // context as possibly undefined to make TypeScript happy. - if (!context) { - throw new Error(tagMessage('Event handler context is not defined')); - } - runCallback(CALLBACK_TYPE.UPDATE, handlers, event); - context.lastUpdateEvent = eventWithData; + if (context) { + context.lastUpdateEvent = eventWithData; + } } export function handleTouchEvent< diff --git a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/useReanimatedEventHandler.ts b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/useReanimatedEventHandler.ts index 76e98c4f70..279236eaa7 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/callbacks/useReanimatedEventHandler.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/callbacks/useReanimatedEventHandler.ts @@ -1,6 +1,9 @@ import { useEffect, useMemo, useRef } from 'react'; -import type { ReanimatedHandler } from '../../../handlers/gestures/reanimatedWrapper'; +import type { + ReanimatedContext, + ReanimatedHandler, +} from '../../../handlers/gestures/reanimatedWrapper'; import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper'; import type { ChangeCalculatorType, @@ -21,6 +24,16 @@ const workletNOOP = () => { // no-op }; +const lastUpdateEventMap = Reanimated?.makeMutable( + new Map>() +); + +function deleteHandlerEventEntry(handlerTag: number) { + 'worklet'; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + lastUpdateEventMap!.value.delete(handlerTag); +} + export function useReanimatedEventHandler< THandlerData, TExtendedHandlerData extends THandlerData, @@ -53,13 +66,21 @@ export function useReanimatedEventHandler< > ) => { 'worklet'; + // If we're on Reanimated path, lastUpdateEventMap should always be defined + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + let context = lastUpdateEventMap!.value.get(event.handlerTag); + if (context === undefined) { + context = { lastUpdateEvent: undefined }; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + lastUpdateEventMap!.value.set(event.handlerTag, context); + } + eventHandler( handlerTag, event, workletizedHandlers, changeEventCalculator, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain - reanimatedHandler?.context!, + context as ReanimatedContext, false, fillInDefaultValues ); @@ -77,6 +98,10 @@ export function useReanimatedEventHandler< // ref from what was actually committed. useEffect(() => { prevHandlerTagRef.current = handlerTag; + + return () => { + Reanimated?.runOnUI?.(deleteHandlerEventEntry)(handlerTag); + }; }, [handlerTag]); const reanimatedEvent = Reanimated?.useEvent(