Skip to content

[General] Don't emit duplicate events for NativeViewGestureHandler#4102

Merged
j-piasecki merged 3 commits intomainfrom
@jpiasecki/native-gesture-dont-emit-duplicate-events
Apr 22, 2026
Merged

[General] Don't emit duplicate events for NativeViewGestureHandler#4102
j-piasecki merged 3 commits intomainfrom
@jpiasecki/native-gesture-dont-emit-duplicate-events

Conversation

@j-piasecki
Copy link
Copy Markdown
Member

Description

Updates NativeViewGestureHandler not to emit update event on every pointer move unless the payload would change. This significantly reduces the number of events dispatched by the native gesture handlers, which should have a positive impact on performance without causing any information loss.

Supersedes #3348

Test plan

Add onUpdate={console.log} to the internal Touchable implementation and check any example using Touchable.

Copilot AI review requested due to automatic review settings April 20, 2026 12:24
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Reduces noise from NativeViewGestureHandler by suppressing redundant “active update” events when the emitted payload hasn’t changed, lowering dispatch overhead across web, iOS, and Android.

Changes:

  • Web: add a per-handler snapshot comparison to suppress duplicate ACTIVE update events for NativeViewGestureHandler.
  • Web: introduce an overridable suppression hook in the base GestureHandler for V3 update events.
  • Native (iOS/Android): suppress redundant ACTIVE update events by snapshotting the payload fields and only emitting on change.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
packages/react-native-gesture-handler/src/web/handlers/NativeViewGestureHandler.ts Tracks last ACTIVE handlerData and suppresses duplicate update events via deep equality.
packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts Adds shouldSuppressActiveUpdate() hook and applies it to V3 ACTIVE update event dispatch.
packages/react-native-gesture-handler/apple/Handlers/RNNativeViewHandler.mm Adds caching of last ACTIVE extraData to avoid emitting unchanged ACTIVE events for UIControl-driven native views.
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt Suppresses duplicate ACTIVE updates by comparing a lightweight snapshot before dispatching handler updates.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@j-piasecki j-piasecki merged commit 118775a into main Apr 22, 2026
8 checks passed
@j-piasecki j-piasecki deleted the @jpiasecki/native-gesture-dont-emit-duplicate-events branch April 22, 2026 07:49
m-bert added a commit that referenced this pull request Apr 28, 2026
## Description

I've noticed that when `Native` gesture is not attached to `UIControl`
it sends different events (e.g. events containing `x` and `y`
positions). I've changed that to mimic behavior from #4102

## Test plan

<details>
<summary>Tested on the following example</summary>

```tsx
import { ScrollView, StyleSheet, View } from 'react-native';
import {
  GestureDetector,
  GestureHandlerRootView,
  ScrollView as RNGHScrollView,
  Touchable,
  useNativeGesture,
} from 'react-native-gesture-handler';

export default function App() {
  const g = useNativeGesture({
    onBegin: () => {
      console.log(Date.now(), 'gesture begin');
    },
    onActivate: () => {
      console.log(Date.now(), 'gesture activate');
    },
    onUpdate: (e) => {
      console.log(Date.now(), 'gesture update', e);
    },
    onDeactivate: () => {
      console.log(Date.now(), 'gesture deactivate');
    },
    onFinalize: () => {
      console.log(Date.now(), 'gesture finalize');
    },
    shouldCancelWhenOutside: false,
  });

  return (
    <GestureHandlerRootView style={styles.container}>
      <Touchable
        cancelOnLeave={false}
        style={{
          width: 150,
          height: 45,
          backgroundColor: 'crimson',
          borderRadius: 8,
        }}
      />

      <GestureDetector gesture={g}>
        <ScrollView style={[styles.scrollView, { backgroundColor: 'green' }]}>
          {BOXES.map((color, i) => (
            <View key={i} style={[styles.box, { backgroundColor: color }]} />
          ))}
        </ScrollView>
      </GestureDetector>
      <RNGHScrollView
        onBegin={() => {
          console.log(Date.now(), 'gesture begin');
        }}
        onActivate={() => {
          console.log(Date.now(), 'gesture activate');
        }}
        onUpdate={() => {
          console.log(Date.now(), 'gesture update');
        }}
        onDeactivate={() => {
          console.log(Date.now(), 'gesture deactivate');
        }}
        onFinalize={() => {
          console.log(Date.now(), 'gesture finalize');
        }}
        style={[styles.scrollView, { backgroundColor: 'blue' }]}>
        {BOXES.map((color, i) => (
          <View key={i} style={[styles.box, { backgroundColor: color }]} />
        ))}
      </RNGHScrollView>
    </GestureHandlerRootView>
  );
}

const BOXES = [
  '#ff6b6b',
  '#ffd93d',
  '#6bcb77',
  '#4d96ff',
  '#c77dff',
  '#ff9f43',
  '#ee5a24',
  '#0652dd',
  '#1289a7',
  '#d980fa',
];

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  scrollView: {
    width: 200,
    maxHeight: 250,
    marginVertical: 8,
  },
  box: {
    width: '100%',
    height: 60,
    marginBottom: 4,
  },
});
```

</details>
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.

3 participants