Skip to content
Open
Show file tree
Hide file tree
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,13 +1,28 @@
import { render, renderHook } from '@testing-library/react-native';
import { act } from 'react';
import React, { act } from 'react';

import GestureHandlerRootView from '../components/GestureHandlerRootView';
import { fireGestureHandler, getByGestureTestId } from '../jestUtils';
import RNGestureHandlerModule from '../RNGestureHandlerModule';
import { State } from '../State';
import { RectButton, Touchable } from '../v3/components';
import { usePanGesture } from '../v3/hooks/gestures';
import type { SingleGesture } from '../v3/types';

async function flushQueuedOperations() {
await act(async () => {
await new Promise<void>((resolve) => {
setImmediate(() => resolve());
});
});
}

afterEach(async () => {
await flushQueuedOperations();
await flushQueuedOperations();
jest.restoreAllMocks();
});

describe('[API v3] Hooks', () => {
test('Pan gesture', () => {
const onBegin = jest.fn();
Expand All @@ -16,8 +31,8 @@
const panGesture = renderHook(() =>
usePanGesture({
disableReanimated: true,
onBegin: (e) => onBegin(e),

Check warning on line 34 in packages/react-native-gesture-handler/src/__tests__/api_v3.test.tsx

View workflow job for this annotation

GitHub Actions / check

Unsafe return of an `any` typed value
onActivate: (e) => onStart(e),

Check warning on line 35 in packages/react-native-gesture-handler/src/__tests__/api_v3.test.tsx

View workflow job for this annotation

GitHub Actions / check

Unsafe return of an `any` typed value
})
).result.current;

Expand All @@ -31,6 +46,32 @@
expect(onBegin).toHaveBeenCalledTimes(1);
expect(onStart).toHaveBeenCalledTimes(1);
});

test('does not drop native handler during StrictMode effect replay', async () => {
const dropGestureHandlerSpy = jest.spyOn(
RNGestureHandlerModule,
'dropGestureHandler'
);

const StrictModeWrapper = ({ children }: React.PropsWithChildren) => (
<React.StrictMode>{children}</React.StrictMode>
);

const { unmount } = renderHook(
() => usePanGesture({ disableReanimated: true }),
{ wrapper: StrictModeWrapper }
);

await flushQueuedOperations();

expect(dropGestureHandlerSpy).not.toHaveBeenCalled();

unmount();
await flushQueuedOperations();
await flushQueuedOperations();

expect(dropGestureHandlerSpy).toHaveBeenCalledTimes(1);
});
});

describe('[API v3] Components', () => {
Expand Down Expand Up @@ -128,9 +169,9 @@
render(<Example />);

const gesture = getByGestureTestId('touchable') as SingleGesture<
any,

Check warning on line 172 in packages/react-native-gesture-handler/src/__tests__/api_v3.test.tsx

View workflow job for this annotation

GitHub Actions / check

Unexpected any. Specify a different type
any,

Check warning on line 173 in packages/react-native-gesture-handler/src/__tests__/api_v3.test.tsx

View workflow job for this annotation

GitHub Actions / check

Unexpected any. Specify a different type
any

Check warning on line 174 in packages/react-native-gesture-handler/src/__tests__/api_v3.test.tsx

View workflow job for this annotation

GitHub Actions / check

Unexpected any. Specify a different type
>;
const { jsEventHandler } = gesture.detectorCallbacks;

Expand Down
29 changes: 24 additions & 5 deletions packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useMemo, useRef } from 'react';

import { ghQueueMicrotask } from '../../ghQueueMicrotask';
import { getNextHandlerTag } from '../../handlers/getNextHandlerTag';
import {
registerGesture,
Expand Down Expand Up @@ -62,6 +63,7 @@ export function useGesture<
);

const currentGestureRef = useRef({ type: '', handlerTag: -1 });
const dropGestureHandlerTokenRef = useRef(0);
if (
currentGestureRef.current.handlerTag !== handlerTag ||
currentGestureRef.current.type !== (type as string)
Expand Down Expand Up @@ -94,13 +96,30 @@ export function useGesture<
);

useEffect(() => {
// React StrictMode replays effects without recreating the hook instance.
// Delay dropping the native handler so the replayed mount can cancel it.
dropGestureHandlerTokenRef.current += 1;
Comment on lines +99 to +101

return () => {
if (currentGestureRef.current.handlerTag === handlerTag) {
currentGestureRef.current = { type: '', handlerTag: -1 };
}
const dropGestureHandlerToken = ++dropGestureHandlerTokenRef.current;

ghQueueMicrotask(() => {
Comment thread
coado marked this conversation as resolved.
const wasSameHandlerRecreated =
dropGestureHandlerTokenRef.current !== dropGestureHandlerToken &&
currentGestureRef.current.handlerTag === handlerTag &&
currentGestureRef.current.type === type;

if (wasSameHandlerRecreated) {
return;
Comment thread
coado marked this conversation as resolved.
}

if (currentGestureRef.current.handlerTag === handlerTag) {
currentGestureRef.current = { type: '', handlerTag: -1 };
}

NativeProxy.dropGestureHandler(handlerTag);
scheduleFlushOperations();
NativeProxy.dropGestureHandler(handlerTag);
scheduleFlushOperations();
});
};
}, [type, handlerTag]);

Expand Down
Loading