Skip to content

Conversation

@M-i-k-e-l
Copy link
Collaborator

@M-i-k-e-l M-i-k-e-l commented Nov 2, 2025

Description

TabController - fix touch events not triggered on real Android device when scrolling with zIndex change
Opened a ticket to reanimated (link).
This is solved in reanimated v4, however we cannot yet migrate to it (and now is not a good time anyway)

Example:

import _ from 'lodash';
import React, {useCallback, useState} from 'react';
import {ScrollView, StyleSheet} from 'react-native';
import Reanimated, {useSharedValue, useAnimatedStyle} from 'react-native-reanimated';
import {GestureDetector, Gesture, GestureHandlerRootView} from 'react-native-gesture-handler';
import {
  View,
  Button,
  Dialog,
  TabController,
  Text,
  Image,
  TouchableOpacity,
  Assets,
  Colors,
  Spacings
} from 'react-native-ui-lib';

// Simple mock data
const mockCards = [
  {id: '1', title: 'Card 1'},
  {id: '2', title: 'Card 2'},
  {id: '3', title: 'Card 3'},
  {id: '4', title: 'Card 4'},
  {id: '5', title: 'Card 5'},
  {id: '6', title: 'Card 6'},
  {id: '7', title: 'Card 7'},
  {id: '8', title: 'Card 8'},
  {id: '9', title: 'Card 9'},
  {id: '10', title: 'Card 10'},
  {id: '11', title: 'Card 11'},
  {id: '12', title: 'Card 12'},
  {id: '13', title: 'Card 13'},
  {id: '14', title: 'Card 14'},
  {id: '15', title: 'Card 15'},
  {id: '16', title: 'Card 16'},
  {id: '17', title: 'Card 17'}
];

const SimpleCard = ({card}: {card: {id: string; title: string}}) => {
  const handlePress = () => {
    console.log('Card pressed:', card.title);
  };

  return (
    <TouchableOpacity onPress={handlePress} style={styles.card}>
      <Text style={styles.cardTitle}>{card.title}</Text>
      <Text style={styles.cardSubtitle}>Press me to test touch events</Text>
    </TouchableOpacity>
  );
};

function PlaygroundScreen() {
  const currentPage = useSharedValue(0);

  const animatedPage0Style = useAnimatedStyle(() => {
    return {
      opacity: currentPage.value === 0 ? 1 : 0,
      zIndex: currentPage.value === 0 ? 1 : 0
    };
  });

  const animatedPage1Style = useAnimatedStyle(() => {
    return {
      opacity: currentPage.value === 1 ? 1 : 0,
      zIndex: currentPage.value === 1 ? 1 : 0
    };
  });

  const setCurrentIndex = useCallback((index: number) => {
    'worklet';
    console.log('setCurrentIndex', index);
    currentPage.value = index;
  }, []);

  return (
    <GestureHandlerRootView>
      <View row>
        <GestureDetector gesture={Gesture.Tap().onEnd(() => setCurrentIndex(0))}>
          <View flex centerH>
            <Text>Left</Text>
          </View>
        </GestureDetector>
        <GestureDetector gesture={Gesture.Tap().onEnd(() => setCurrentIndex(1))}>
          <View flex centerH>
            <Text>Right</Text>
          </View>
        </GestureDetector>
      </View>
      <View flex>
        <View reanimated key={'page0'} style={[StyleSheet.absoluteFillObject, animatedPage0Style]}>
          <ScrollView>
            {_.map(mockCards, card => (
              <SimpleCard key={card.id} card={{id: card.id, title: `Page 0 ${card.title}`}}/>
            ))}
          </ScrollView>
        </View>
        <View reanimated key={'page1'} style={[StyleSheet.absoluteFillObject, animatedPage1Style]}>
          <ScrollView>
            {_.map(mockCards, card => (
              <SimpleCard key={card.id} card={{id: card.id, title: `Page 1 ${card.title}`}}/>
            ))}
          </ScrollView>
        </View>
      </View>
    </GestureHandlerRootView>
  );
}

export default PlaygroundScreen;

const styles = StyleSheet.create({
  header: {
    height: 150,
    backgroundColor: Colors.$backgroundPrimaryHeavy
  },
  headerImage: {
    width: '100%',
    height: '100%'
  },
  headerOverlay: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: Colors.$backgroundOverlay,
    justifyContent: 'center',
    alignItems: 'center'
  },
  headerTitle: {
    fontSize: 24,
    fontWeight: 'bold',
    color: Colors.$textDefault
  },
  scrollView: {
    flex: 1
  },
  content: {
    padding: Spacings.s4
  },
  card: {
    backgroundColor: Colors.$backgroundElevated,
    padding: Spacings.s5,
    marginBottom: Spacings.s4,
    minHeight: 80,
    borderWidth: 2,
    borderColor: Colors.$outlinePrimary
  },
  cardTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: Spacings.s2
  },
  cardSubtitle: {
    fontSize: 14,
    color: Colors.$textNeutral
  }
});

Changelog

TabController - fix touch events not triggered on real Android devicewhen scrolling with zIndex change

Additional info

Ticket 4838

@github-actions
Copy link
Contributor

github-actions bot commented Nov 2, 2025

✅ PR Description Validation Passed

All required sections are properly filled out:

  • Description
  • Changelog
  • Additional info

Your PR is good for review! 🚀


This validation ensures all sections from the PR template are properly filled.

return [!asCarousel && styles.page, animatedPageStyle, {width: asCarousel ? containerWidth : undefined}, style];
}, [asCarousel, animatedPageStyle, containerWidth, style]);

if (!isActive && !asCarousel) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

By returning null you cancel the TabController behavior of keeping pages "alive" (but hidden)
So each tab change will remount the tab page children
In member view for example, it will load all the page sections and will generally degrade the performance

Copy link
Collaborator Author

@M-i-k-e-l M-i-k-e-l Nov 4, 2025

Choose a reason for hiding this comment

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

Changed to opacity + zIndex change in a regular style, seems to work (although it is above the RN warning\error - not sure how it was before).
Need to do some further tests, so change to a draft

@ethanshar ethanshar assigned M-i-k-e-l and unassigned ethanshar Nov 4, 2025
@M-i-k-e-l M-i-k-e-l marked this pull request as draft November 4, 2025 14:00
@M-i-k-e-l M-i-k-e-l marked this pull request as ready for review November 4, 2025 17:16
@M-i-k-e-l M-i-k-e-l assigned ethanshar and unassigned M-i-k-e-l Nov 4, 2025
@M-i-k-e-l M-i-k-e-l requested a review from ethanshar November 4, 2025 17:20
Copy link
Collaborator

@ethanshar ethanshar left a comment

Choose a reason for hiding this comment

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

Approved, but..
I would keep the old code in a comment with a TODO to revert back to it, because this is temp solution
I would also check how this change behave in a complex scenario like member view where we have tab controller with many tabs (wrapped in a CollapsingHeader)
And last, I would create a ticket to revert this once you upgrade to new Reanimated

@ethanshar ethanshar assigned M-i-k-e-l and unassigned ethanshar Nov 5, 2025
@M-i-k-e-l
Copy link
Collaborator Author

Approved, but.. I would keep the old code in a comment with a TODO to revert back to it, because this is temp solution I would also check how this change behave in a complex scenario like member view where we have tab controller with many tabs (wrapped in a CollapsingHeader) And last, I would create a ticket to revert this once you upgrade to new Reanimated

Lidor has tested it. There is already a lot of commented code so I'm not sure keeping it is the way to go, I did add a TODO (TODO: RN 77 is something we need to search for and fix later on), I thought we've created a ticket for the TODO but I cannot find it, I'll create one.

@M-i-k-e-l M-i-k-e-l merged commit 77681ec into v8 Nov 5, 2025
2 checks passed
@M-i-k-e-l M-i-k-e-l deleted the fix/android-touch-not-triggered-when-zindex-and-scroll-2 branch November 5, 2025 09: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.

3 participants