Skip to content

[Release] 2.31.2 cherry-pick thread - #4142

Merged
m-bert merged 5 commits into
v2-stablefrom
@mbert/2.31.2-cherry-picks-thread
May 4, 2026
Merged

m-bert merged 5 commits into
v2-stablefrom
@mbert/2.31.2-cherry-picks-thread

Conversation

@m-bert

@m-bert m-bert commented May 4, 2026

Copy link
Copy Markdown
Collaborator

m-bert and others added 5 commits May 4, 2026 11:51
## Description

I've noticed that `Pinch` and `Rotation` behave weirdly when it comes to
ending gesture on web. I've took a look and fixed some minor issues.

> [!NOTE]
> This PR may introduce mismatch between `android` and `web` versions of
`Rotation`. I still think that this is how it is supposed to work, so we
may want to [make required changes to `android` as
well](#4079)
(or drop this PR if you prefer to).

### Pinch

I've noticed that `Pinch` is not reset when pointers are placed on view
and then released. This was because for some reason `Pinch` simply
ignored finished states if it had not activated earlier.

### Rotation 

`Rotation`, always finalized with `true`. This was because we always
tried to move it to `end` state. Our internal event handler hadn't been
sending `onDeactivate`, but `true` was still passed into `onFinalize`.

## Test plan

<details>
<summary>Tested on Transformations example and code below:</summary>

```tsx
import { StyleSheet, View } from 'react-native';
import {
  GestureDetector,
  GestureHandlerRootView,
  usePinchGesture,
  useRotationGesture,
} from 'react-native-gesture-handler';

export default function App() {
  const g = useRotationGesture({
    onBegin: () => console.log('onBegin'),
    onActivate: () => console.log('onActivate'),
    onUpdate: (e) => console.log('onUpdate', e.rotation),
    onDeactivate: () => console.log('onDeactivate'),
    onFinalize: (_, s) => console.log('onFinalize', s),
  });

  return (
    <GestureHandlerRootView style={styles.container}>
      <GestureDetector gesture={g}>
        <View style={styles.box} />
      </GestureDetector>
    </GestureHandlerRootView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  box: {
    width: 200,
    height: 200,
    backgroundColor: 'blue',
    borderRadius: 12,
  },
});
```

</details>
## Description

Follow up for #4078 

I've noticed that when `Rotation` does not activate, but pointers are
released, `onFinalize` is called with `true`. This happens because we
try to move it to `END` state, but our internal logic does not send
`onDeactivate`.

Code in `onHandle` that I removed was effectively dead, as `ACTION_UP`
was handled in rotation detector - the only possible branch was `else`
statement which fails handler if only one pointer was present.


## Test plan

<details>
<summary>Tested on Transformations example and the following
code:</summary>

```tsx
import { StyleSheet, View } from 'react-native';
import {
  GestureDetector,
  GestureHandlerRootView,
  usePinchGesture,
  useRotationGesture,
} from 'react-native-gesture-handler';

export default function App() {
  const g = useRotationGesture({
    onBegin: () => console.log('onBegin'),
    onActivate: () => console.log('onActivate'),
    onUpdate: (e) => console.log('onUpdate', e.rotation),
    onDeactivate: () => console.log('onDeactivate'),
    onFinalize: (_, s) => console.log('onFinalize', s),
  });

  return (
    <GestureHandlerRootView style={styles.container}>
      <GestureDetector gesture={g}>
        <View style={styles.box} />
      </GestureDetector>
    </GestureHandlerRootView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  box: {
    width: 200,
    height: 200,
    backgroundColor: 'blue',
    borderRadius: 12,
  },
});
```
</details>
# Fix ReanimatedSwipeable ref type to support ref callbacks

## Description

This PR fixes the `ref` prop type in `ReanimatedSwipeableProps` to
support both ref objects and ref callbacks, improving TypeScript
compatibility and developer experience.

## Changes

- Changed `ref?: React.RefObject<SwipeableMethods | null>` to `ref?:
React.Ref<SwipeableMethods | null>` in `ReanimatedSwipeableProps`
interface

## Motivation

The current implementation only supports `React.RefObject` which limits
developers to using `useRef()` hook. By changing to `React.Ref`, the
component now supports:

1. **Ref objects** (`useRef()`)
2. **Ref callbacks** (function refs)

This change aligns with React's best practices and provides more
flexibility for developers when working with refs.

## Example Usage

### Before (only ref objects)
```tsx
const swipeableRef = useRef<SwipeableMethods>(null);

<ReanimatedSwipeable ref={swipeableRef}>
  {/* content */}
</ReanimatedSwipeable>
```

### After (supports both ref objects and callbacks)
```tsx
// Ref object
const swipeableRef = useRef<SwipeableMethods>(null);

// Ref callback
const handleRef = (ref: SwipeableMethods | null) => {
  // Custom logic
};

<ReanimatedSwipeable ref={swipeableRef}>
  {/* content */}
</ReanimatedSwipeable>

<ReanimatedSwipeable ref={handleRef}>
  {/* content */}
</ReanimatedSwipeable>
```

## Type Safety

This change maintains full TypeScript type safety while expanding the
supported ref patterns. The `React.Ref<T>` type is a union type that
includes:
- `React.RefObject<T>`
- `React.RefCallback<T>`

## Breaking Changes

None. This is a backward-compatible change that only expands the
supported ref types.

## Testing

- [x] Existing tests pass
- [x] TypeScript compilation succeeds
- [x] Both ref object and ref callback patterns work correctly

## Related Issues

Fixes the limitation where developers could only use `useRef()` with
ReanimatedSwipeable components.

---------

Co-authored-by: Michał <michal.bert@swmansion.com>
Co-authored-by: Michał Bert <63123542+m-bert@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
## Description

In some environments babel plugin is not used (e.g. it can be omitted
when using `Next.js`). Our detector checks if callbacks are `worklets`
by checking `__workletHash` field. However, if plugin wasn't used, this
will be `undefined`. In that case, our warning will wrongly suggest to
use `runOnJS`. This may be misleading, if plugin was skipped on `web`,
but used on native platforms.

In this PR, we check if plugin was applied by checking empty worklet
function. If it has `__workletHash` field it means that plugin was used.
If it wasn't, we won't suggest using `runOnJS`.

## Test plan

Confirmed by the original reporter
When TalkBack is activated, pressable cannot be clicked on android. This
PR fixes the issue.

The reason was that Pressable requires a specific order of events. This
order of events is hardcoded and found empirically. However, with
talback enabled the order of events is different, I added a function
which queries native side whether or not talback is enabled and adjusts
expected event order accordingly.
Moreover with talback enabled longPress gets both onPointerUp and
onPointerDown before native side receives its events, thus we must not
reset the state machine state on onPointerUp.

Tested on the following example:

<details>

```tsx

import React from 'react';
import { Text, StyleSheet, View } from 'react-native';
import { Pressable, GestureHandlerRootView } from 'react-native-gesture-handler'

const PressableExample = () => {
  const [count, setCount] = React.useState(0);
  return (
    <GestureHandlerRootView style={styles.container}>
      <Pressable onPress={() => setCount((c) => c + 1)}>
        <View style={styles.pressable}>
          <Text>{count}</Text>
        </View>
      </Pressable>
    </GestureHandlerRootView >
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  pressable: {
    alignItems: 'center', justifyContent: 'center', backgroundColor: 'green', width: 100, height: 100
  },
  wrapperCustom: {
    borderRadius: 8,
    padding: 16,
    minWidth: 150,
    alignItems: 'center',
  },
  text: {
    fontSize: 18,
    color: 'white',
    fontWeight: '600',
  },
});

export default PressableExample;

```

</details>
Copilot AI review requested due to automatic review settings May 4, 2026 10:00
@m-bert m-bert changed the title @mbert/2.31.2 cherry picks thread [Release] 2.31.2 cherry-pick thread May 4, 2026
@m-bert
m-bert requested review from j-piasecki and removed request for Copilot May 4, 2026 10:01
@m-bert
m-bert merged commit e85cdfe into v2-stable May 4, 2026
10 checks passed
@m-bert
m-bert deleted the @mbert/2.31.2-cherry-picks-thread branch May 4, 2026 10:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants