Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Animated.View breaks RN's Touchables when inside forwardRef's render function #6744

Open
latekvo opened this issue Nov 21, 2024 · 2 comments
Labels
Missing repro This issue need minimum repro scenario Platform: Android This issue is specific to Android Platform: iOS This issue is specific to iOS

Comments

@latekvo
Copy link
Contributor

latekvo commented Nov 21, 2024

Description

When Animated.View is either the root element, or an n+1 child (2nd, 3rd) of the root in a component created using forwardRef, the very first native interactive element is disabled, no longer calling onPress callbacks.

derived from this issue: software-mansion/react-native-gesture-handler#3223

invalid behaviour inspection of the invalid behaviour
Screen.Recording.2024-11-21.at.17.46.28.mov
Screen.Recording.2024-11-21.at.17.46.45.mov

Reproduction code

Expected behaviour example
import React, { forwardRef, useCallback } from 'react';
import Animated from 'react-native-reanimated';
import {
  Text,
  I18nManager,
  StyleSheet,
  TouchableOpacity,
  View,
} from 'react-native';

interface SwipeableMethods {
  close: () => void;
  openLeft: () => void;
  openRight: () => void;
  reset: () => void;
}

const Example = forwardRef<SwipeableMethods, unknown>((_props, _ref) => {
  const leftElement = useCallback(
    () => (
      <View
        style={{
          ...StyleSheet.absoluteFillObject,
          flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
        }}>
        <TouchableOpacity
          onPress={() => {
            console.log('press left');
          }}>
          <View style={{ height: 80, width: 80, backgroundColor: 'yellow' }}>
            <Text>Left</Text>
          </View>
        </TouchableOpacity>
      </View>
    ),
    []
  );

  const rightElement = useCallback(
    () => (
      <View
        style={{
          ...StyleSheet.absoluteFillObject,
          flexDirection: I18nManager.isRTL ? 'row' : 'row-reverse',
        }}>
        <TouchableOpacity
          onPress={() => {
            console.log('press right');
          }}>
          <View style={{ height: 80, width: 80, backgroundColor: 'magenta' }}>
            <Text>Right</Text>
          </View>
        </TouchableOpacity>
      </View>
    ),
    []
  );

  return (
    <View>
      {leftElement()}
      {rightElement()}
      <View
        style={{
          transform: [{ translateX: 0 }, { scale: 0.5 }],
          width: 393,
          height: 80,
          backgroundColor: 'blue',
        }}
      />
    </View>
  );
});

export default Example;
Invalid behaviour, left element non-interactive, `Animated.View` wrapping the right element
import React, { forwardRef, useCallback } from 'react';
import Animated from 'react-native-reanimated';
import {
  Text,
  I18nManager,
  StyleSheet,
  TouchableOpacity,
  View,
} from 'react-native';

interface SwipeableMethods {
  close: () => void;
  openLeft: () => void;
  openRight: () => void;
  reset: () => void;
}

const Example = forwardRef<SwipeableMethods, unknown>((_props, _ref) => {
  const leftElement = useCallback(
    () => (
      <View
        style={{
          ...StyleSheet.absoluteFillObject,
          flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
        }}>
        <TouchableOpacity
          onPress={() => {
            console.log('press left');
          }}>
          <View style={{ height: 80, width: 80, backgroundColor: 'yellow' }}>
            <Text>Left</Text>
          </View>
        </TouchableOpacity>
      </View>
    ),
    []
  );

  const rightElement = useCallback(
    () => (
      <Animated.View
        style={{
          ...StyleSheet.absoluteFillObject,
          flexDirection: I18nManager.isRTL ? 'row' : 'row-reverse',
        }}>
        <TouchableOpacity
          onPress={() => {
            console.log('press right');
          }}>
          <View style={{ height: 80, width: 80, backgroundColor: 'magenta' }}>
            <Text>Right</Text>
          </View>
        </TouchableOpacity>
      </Animated.View>
    ),
    []
  );

  return (
    <View>
      {leftElement()}
      {rightElement()}
      <View
        style={{
          transform: [{ translateX: 0 }, { scale: 0.5 }],
          width: 393,
          height: 80,
          backgroundColor: 'blue',
        }}
      />
    </View>
  );
});

export default Example;
Invalid behaviour, left element non-interactive, `Animated.View` is the root element
import React, { forwardRef, useCallback } from 'react';
import Animated from 'react-native-reanimated';
import {
  Text,
  I18nManager,
  StyleSheet,
  TouchableOpacity,
  View,
} from 'react-native';

interface SwipeableMethods {
  close: () => void;
  openLeft: () => void;
  openRight: () => void;
  reset: () => void;
}

const Example = forwardRef<SwipeableMethods, unknown>((_props, _ref) => {
  const leftElement = useCallback(
    () => (
      <View
        style={{
          ...StyleSheet.absoluteFillObject,
          flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
        }}>
        <TouchableOpacity
          onPress={() => {
            console.log('press left');
          }}>
          <View style={{ height: 80, width: 80, backgroundColor: 'yellow' }}>
            <Text>Left</Text>
          </View>
        </TouchableOpacity>
      </View>
    ),
    []
  );

  const rightElement = useCallback(
    () => (
      <View
        style={{
          ...StyleSheet.absoluteFillObject,
          flexDirection: I18nManager.isRTL ? 'row' : 'row-reverse',
        }}>
        <TouchableOpacity
          onPress={() => {
            console.log('press right');
          }}>
          <View style={{ height: 80, width: 80, backgroundColor: 'magenta' }}>
            <Text>Right</Text>
          </View>
        </TouchableOpacity>
      </View>
    ),
    []
  );

  return (
    <Animated.View>
      {leftElement()}
      {rightElement()}
      <View
        style={{
          transform: [{ translateX: 0 }, { scale: 0.5 }],
          width: 393,
          height: 80,
          backgroundColor: 'blue',
        }}
      />
    </Animated.View>
  );
});

export default Example;
Expected behaviour, `Animated.View` wrapping the left element
import React, { forwardRef, useCallback } from 'react';
import Animated from 'react-native-reanimated';
import {
  Text,
  I18nManager,
  StyleSheet,
  TouchableOpacity,
  View,
} from 'react-native';

interface SwipeableMethods {
  close: () => void;
  openLeft: () => void;
  openRight: () => void;
  reset: () => void;
}

const Example = forwardRef<SwipeableMethods, unknown>((_props, _ref) => {
  const leftElement = useCallback(
    () => (
      <Animated.View
        style={{
          ...StyleSheet.absoluteFillObject,
          flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
        }}>
        <TouchableOpacity
          onPress={() => {
            console.log('press left');
          }}>
          <View style={{ height: 80, width: 80, backgroundColor: 'yellow' }}>
            <Text>Left</Text>
          </View>
        </TouchableOpacity>
      </Animated.View>
    ),
    []
  );

  const rightElement = useCallback(
    () => (
      <View
        style={{
          ...StyleSheet.absoluteFillObject,
          flexDirection: I18nManager.isRTL ? 'row' : 'row-reverse',
        }}>
        <TouchableOpacity
          onPress={() => {
            console.log('press right');
          }}>
          <View style={{ height: 80, width: 80, backgroundColor: 'magenta' }}>
            <Text>Right</Text>
          </View>
        </TouchableOpacity>
      </View>
    ),
    []
  );

  return (
    <View>
      {leftElement()}
      {rightElement()}
      <View
        style={{
          transform: [{ translateX: 0 }, { scale: 0.5 }],
          width: 393,
          height: 80,
          backgroundColor: 'blue',
        }}
      />
    </View>
  );
});

export default Example;

Steps to reproduce

  1. Run the provided examples in either the rngh or the rea example apps, in place of the empty example

Snack or a link to a repository

https://github.com/software-mansion/react-native-gesture-handler/blob/main/example/src/empty/EmptyExample.tsx

Reanimated version

3.16.2

React Native version

0.74.1

Platforms

Android, iOS

JavaScript runtime

Hermes

Workflow

Expo Dev Client

Architecture

Paper (Old Architecture)

Build type

Debug app & dev bundle

Device

Real device, emulator

Device model

Xiaomi POCO X3 Pro, Pixel 3a emulator

Acknowledgements

Yes

Copy link

Hey! 👋

The issue doesn't seem to contain a minimal reproduction.

Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?

@github-actions github-actions bot added Missing repro This issue need minimum repro scenario Platform: Android This issue is specific to Android Platform: iOS This issue is specific to iOS labels Nov 21, 2024
@latekvo latekvo changed the title Animated.View breaks native interactive elements when inside forwardRef's render function Animated.View breaks RN's Touchables when inside forwardRef's render function Nov 22, 2024
@Ademivaldo
Copy link

Same problem found on Android 13 for me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Missing repro This issue need minimum repro scenario Platform: Android This issue is specific to Android Platform: iOS This issue is specific to iOS
Projects
None yet
Development

No branches or pull requests

2 participants