Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo } from 'react';
import { useEffect, useMemo, useRef } from 'react';

import type { ReanimatedHandler } from '../../../handlers/gestures/reanimatedWrapper';
import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper';
Expand All @@ -10,6 +10,12 @@ import type {
} from '../../types';
import { eventHandler } from './eventHandler';

const REANIMATED_EVENT_NAMES = [
'onGestureHandlerReanimatedEvent',
'onGestureHandlerReanimatedStateChange',
'onGestureHandlerReanimatedTouchEvent',
];

const workletNOOP = () => {
'worklet';
// no-op
Expand Down Expand Up @@ -59,14 +65,24 @@ export function useReanimatedEventHandler<
);
};

// Fast Refresh invalidates `useMemo` caches but preserves `useRef`, so the
// `handlerTag` computed with `useMemo([])` in `useGesture` can regenerate
// on FR. Without forcing a rebuild, the registered worklet keeps the old
// `handlerTag` in its closure and `isEventForHandlerWithTag` rejects every
// event emitted by the freshly-created native handler.
const prevHandlerTagRef = useRef(handlerTag);
const handlerTagChanged = prevHandlerTagRef.current !== handlerTag;

// Write after commit so interrupted or re-invoked renders don't desync the
// ref from what was actually committed.
useEffect(() => {
prevHandlerTagRef.current = handlerTag;
}, [handlerTag]);

const reanimatedEvent = Reanimated?.useEvent(
callback,
[
'onGestureHandlerReanimatedEvent',
'onGestureHandlerReanimatedStateChange',
'onGestureHandlerReanimatedTouchEvent',
],
!!reanimatedHandler?.doDependenciesDiffer
REANIMATED_EVENT_NAMES,
handlerTagChanged || !!reanimatedHandler?.doDependenciesDiffer
);
Comment on lines 82 to 86
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a unit test for this regression by mocking Reanimated.useEvent and asserting the third argument (rebuild) flips to true when handlerTag changes between renders (simulating Fast Refresh preserving refs). This would prevent future changes from reintroducing the stale-closure issue.

Copilot uses AI. Check for mistakes.

return reanimatedEvent;
Expand Down
Loading