Skip to content

Commit 802a15b

Browse files
authored
[General] Fix gestures becoming unresponsive after a fast refresh (#4096)
## Description Fast refresh was able to make gestures unresponsive - Gesture Handler uses `useMemo` to store the handler tag, which gets invalidated on the fast refresh. Reanimated's `useHandler`/`useEvent` preserves their internal state, and the worklet event handler didn't rebuild. This caused a gesture with a new tag to use the event handler built for the previous tag, which was ignoring all incoming events. This PR fixes this by tracking handler tag changes and rebuilding the event handler when it changes. Supersedes #3997 ## Test plan <details> <summary>File1.tsx</summary> ```jsx import React from 'react'; import { View } from 'react-native'; import { GestureDetector, useTapGesture } from 'react-native-gesture-handler'; function Test() { const gesture = useTapGesture({ onActivate: () => { console.log('onActivate'); }, }); const COMMENT_THIS = 2; return ( <GestureDetector gesture={gesture}> <View style={{ width: 100, height: 100, backgroundColor: 'red' }} /> </GestureDetector> ); } export default Test; ``` </details> <details> <summary>File2.tsx</summary> ```jsx import React from 'react'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import Test from './File1'; function Example() { return ( <GestureHandlerRootView> <Test /> </GestureHandlerRootView> ); } export default Example; ``` </details> 1. Navigate to `File2` 2. Verify that tap works 3. Comment `COMMENT_THIS` and save `File1` 4. Check whether the tap works
1 parent ba999e0 commit 802a15b

1 file changed

Lines changed: 23 additions & 7 deletions

File tree

packages/react-native-gesture-handler/src/v3/hooks/callbacks/useReanimatedEventHandler.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useMemo } from 'react';
1+
import { useEffect, useMemo, useRef } from 'react';
22

33
import type { ReanimatedHandler } from '../../../handlers/gestures/reanimatedWrapper';
44
import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper';
@@ -10,6 +10,12 @@ import type {
1010
} from '../../types';
1111
import { eventHandler } from './eventHandler';
1212

13+
const REANIMATED_EVENT_NAMES = [
14+
'onGestureHandlerReanimatedEvent',
15+
'onGestureHandlerReanimatedStateChange',
16+
'onGestureHandlerReanimatedTouchEvent',
17+
];
18+
1319
const workletNOOP = () => {
1420
'worklet';
1521
// no-op
@@ -59,14 +65,24 @@ export function useReanimatedEventHandler<
5965
);
6066
};
6167

68+
// Fast Refresh invalidates `useMemo` caches but preserves `useRef`, so the
69+
// `handlerTag` computed with `useMemo([])` in `useGesture` can regenerate
70+
// on FR. Without forcing a rebuild, the registered worklet keeps the old
71+
// `handlerTag` in its closure and `isEventForHandlerWithTag` rejects every
72+
// event emitted by the freshly-created native handler.
73+
const prevHandlerTagRef = useRef(handlerTag);
74+
const handlerTagChanged = prevHandlerTagRef.current !== handlerTag;
75+
76+
// Write after commit so interrupted or re-invoked renders don't desync the
77+
// ref from what was actually committed.
78+
useEffect(() => {
79+
prevHandlerTagRef.current = handlerTag;
80+
}, [handlerTag]);
81+
6282
const reanimatedEvent = Reanimated?.useEvent(
6383
callback,
64-
[
65-
'onGestureHandlerReanimatedEvent',
66-
'onGestureHandlerReanimatedStateChange',
67-
'onGestureHandlerReanimatedTouchEvent',
68-
],
69-
!!reanimatedHandler?.doDependenciesDiffer
84+
REANIMATED_EVENT_NAMES,
85+
handlerTagChanged || !!reanimatedHandler?.doDependenciesDiffer
7086
);
7187

7288
return reanimatedEvent;

0 commit comments

Comments
 (0)