Skip to content
Merged
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
2 changes: 2 additions & 0 deletions packages/docs-gesture-handler/docs/components/buttons.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Gesture Handler library exposes components that render native touchable elements

On top of that all the buttons are wrapped with [`Native gesture`](/docs/gestures/use-native-gesture) and therefore allow for all its [properties](/docs/gestures/use-native-gesture#config) to be applied to them.

Button callbacks always run on the JS thread. The Reanimated-specific configuration ([`disableReanimated`](/docs/fundamentals/reanimated-interactions#disablereanimated), [`runOnJS`](/docs/fundamentals/reanimated-interactions#runonjs)) is not available on buttons, and passing a worklet as a callback does not move it to the UI thread.


## RawButton

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ sidebar_position: 5

Some components come with a [`Native`](/docs/gestures/use-native-gesture) gesture pre-applied. This allows them to participate in the gesture recognition process. Have a look at the example below.

The callbacks of the pre-applied gesture always run on the JS thread. The Reanimated-specific configuration ([`disableReanimated`](/docs/fundamentals/reanimated-interactions#disablereanimated), [`runOnJS`](/docs/fundamentals/reanimated-interactions#runonjs)) is not available on these components.

```tsx
import { useState } from 'react';
import { Switch } from 'react-native';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@
const panGesture = renderHook(() =>
usePanGesture({
disableReanimated: true,
onBegin: (e) => onBegin(e),

Check warning on line 62 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 63 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 Down Expand Up @@ -88,8 +88,8 @@
const panGesture = renderHook(() =>
usePanGesture({
disableReanimated: true,
onUpdate: (e) => onUpdate(e),

Check warning on line 91 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
onTouchesUp: (e) => onTouchesUp(e),

Check warning on line 92 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 Down Expand Up @@ -267,6 +267,40 @@
expect(pressFn).toHaveBeenCalledTimes(1);
});

// BaseButton adds plain JS callbacks of its own, so a worklet passed by the
// user must not move the gesture to the Reanimated detector (#4628).
test('Rect Button with a worklet callback stays on the JS thread', () => {
const pressFn = jest.fn();
const onTouchesDown = Object.assign(jest.fn(), { __workletHash: 1 });

render(
<GestureHandlerRootView>
<RectButton
testID="btn"
// @ts-expect-error Reanimated config is not part of the wrapper props.
disableReanimated={false}
onTouchesDown={onTouchesDown}
onPress={pressFn}
/>
</GestureHandlerRootView>
);

const nativeGesture = getByGestureTestId('btn');

expect(nativeGesture.config.shouldUseReanimatedDetector).toBe(false);
expect(nativeGesture.config.disableReanimated).toBe(true);

act(() => {
fireGestureHandler(nativeGesture, [
{ oldState: State.UNDETERMINED, state: State.BEGAN },
{ oldState: State.BEGAN, state: State.ACTIVE },
{ oldState: State.ACTIVE, state: State.END },
]);
});

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

describe('ScrollView', () => {
test('handles responder event passed through Pressable for keyboardShouldPersistTaps handled', async () => {
const { UNSAFE_getAllByType } = render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { GestureDetectorType, InterceptingGestureDetector } from './detectors';
import { NativeDetector } from './detectors/NativeDetector';
import { VirtualDetector } from './detectors/VirtualDetector/VirtualDetector';
import { useNativeGesture } from './hooks/gestures';
import type { NativeGestureConfig } from './hooks/gestures/native/NativeTypes';
import { NativeWrapperProps } from './hooks/utils';
import type {
NativeWrapperProperties,
Expand Down Expand Up @@ -36,7 +37,7 @@ export default function createNativeWrapper<TRef = unknown, TProps = unknown>(
return res;
},
{
gestureHandlerProps: { ...config }, // Watch out not to modify config
gestureHandlerProps: { ...config } as NativeGestureConfig, // Watch out not to modify config
childProps: {
enabled: props.enabled,
hitSlop: props.hitSlop,
Expand All @@ -45,9 +46,10 @@ export default function createNativeWrapper<TRef = unknown, TProps = unknown>(
}
);

if (gestureHandlerProps.disableReanimated === undefined) {
gestureHandlerProps.disableReanimated = true;
}
// Components built on top of the wrapper (e.g. BaseButton) add plain JS
// callbacks of their own, so the gesture must never use the Reanimated
// detector, whatever the caller passed.
gestureHandlerProps.disableReanimated = true;

const native = useNativeGesture(gestureHandlerProps);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,15 @@ export const PropsWhiteLists = new Map<

export const EMPTY_WHITE_LIST = new Set<string>();

// `disableReanimated` and `runOnJS` are not wrapper props, but route them to
// the gesture (where the wrapper overrides them) instead of the wrapped view.
export const NativeWrapperProps = new Set<
keyof NativeWrapperProperties<unknown>
keyof NativeWrapperProperties<unknown> | 'disableReanimated' | 'runOnJS'
>([
...CommonConfig,
...HandlerCallbacks,
...NativeHandlerNativeProperties,
...ExternalRelationsConfig,
'disableReanimated',
'runOnJS',
]);
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ export type WrapperSpecificProperties<T = unknown> = {
) => void;
};

export type NativeWrapperProperties<T = unknown> = CommonGestureConfig &
// Wrapped components always run their callbacks on the JS thread, so the
// Reanimated-only config has no effect on them.
export type NativeWrapperProperties<T = unknown> = Omit<
CommonGestureConfig,
'disableReanimated' | 'runOnJS'
> &
GestureCallbacks<NativeHandlerData> &
NativeGestureNativeProperties &
ExternalRelations &
Expand Down
Loading