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

useAnimatedStyle causing focus and button press issues in FlatList #6704

Open
ravis-farooq opened this issue Nov 15, 2024 · 0 comments
Open
Labels
Platform: Android This issue is specific to Android Repro provided A reproduction with a snippet of code, snack or repo is provided

Comments

@ravis-farooq
Copy link

Description

I am encountering a problem when using animations with FlatList. Specifically, when I use Animated or useAnimatedStyle from Reanimated for tab animations, the following issues occur:

The TextInput in the second tab (and onwards) doesn't gain focus when tapped.
Any TouchableOpacity or button inside the FlatList items also becomes unresponsive.
When I switched from Reanimated's useAnimatedStyle to Animated from React Native, the issue was resolved, and everything worked as expected. This appears to be a problem with how useAnimatedStyle interacts with FlatList.

Steps to reproduce

Copy the code below into a fresh React Native project.
Run the app.
Try focusing on the TextInput in the second tab or pressing any button inside the second tab.
Comment out Reanimated usage and use React Native's Animated instead to observe the difference.

import React, { useState, useRef } from 'react';
import {
  View,
  TextInput,
  FlatList,
  StyleSheet,
  Dimensions,
  Animated,
  TouchableOpacity,
} from 'react-native';
import { Box, Typography } from '@shopify/restyle';

const { width } = Dimensions.get('window');
const spacing = 16;

type TabData = {
  id: number;
  title: string;
  placeholder: string;
};

const CustomTabView = () => {
  const [tabsData] = useState<TabData[]>([
    { id: 1, title: 'Tab 1', placeholder: 'Enter text for Tab 1' },
    { id: 2, title: 'Tab 2', placeholder: 'Enter text for Tab 2' },
    { id: 3, title: 'Tab 3', placeholder: 'Enter text for Tab 3' },
  ]);

  const [tabValues, setTabValues] = useState<{ [key: number]: string }>({
    1: '',
    2: '',
    3: '',
  });

  const [activeTab, setActiveTab] = useState(0);
  const flatListRef = useRef<FlatList>(null);
  const animatedIndicator = useRef(new Animated.Value(0)).current;

  const handleTabPress = (index: number) => {
    setActiveTab(index);

    // Animate the indicator to the selected tab
    Animated.timing(animatedIndicator, {
      toValue: index * (width / tabsData.length),
      duration: 250,
      useNativeDriver: false,
    }).start();

    flatListRef.current?.scrollToIndex({
      animated: true,
      index,
    });
  };

  const handleInputChange = (id: number, value: string) => {
    setTabValues((prevState) => ({ ...prevState, [id]: value }));
  };

  return (
    <View style={styles.container}>
      {/* Tab Buttons with Animated Indicator */}
      <Box
        flexDirection="row"
        justifyContent="space-around"
        bg="gray200"
        position="absolute"
        width={width - spacing}
        top={0}
        zIndex={1}
        borderRadius="s"
        overflow="hidden"
        style={{
          padding: spacing / 3,
          marginHorizontal: spacing / 2,
        }}
      >
        <Animated.View
          style={[
            styles.indicator,
            {
              width: width / tabsData.length,
              transform: [{ translateX: animatedIndicator }],
            },
          ]}
        />
        {tabsData.map((tab, index) => (
          <TouchableOpacity
            key={index}
            style={styles.tabButton}
            onPress={() => handleTabPress(index)}
          >
            <Typography
              textAlign="center"
              fontFamily="medium"
              color={activeTab === index ? 'globalWhite' : 'textSecondary'}
            >
              {tab.title}
            </Typography>
          </TouchableOpacity>
        ))}
      </Box>

      {/* Content for Each Tab */}
      <FlatList
        ref={flatListRef}
        data={tabsData}
        renderItem={({ item }) => (
          <View style={styles.tabContainer} key={item.id}>
            <TextInput
              style={styles.textInput}
              placeholder={item.placeholder}
              value={tabValues[item.id]}
              onChangeText={(text) => handleInputChange(item.id, text)}
              onFocus={() => console.log(`Focused on Tab ${item.id}`)}
            />
          </View>
        )}
        keyExtractor={(item) => item.id.toString()}
        horizontal
        showsHorizontalScrollIndicator={false}
        pagingEnabled
        keyboardShouldPersistTaps="always"
        removeClippedSubviews={false} // Important to ensure inputs offscreen remain interactive
        onMomentumScrollEnd={(event) => {
          const index = Math.floor(event.nativeEvent.contentOffset.x / width);
          setActiveTab(index);
          Animated.timing(animatedIndicator, {
            toValue: index * (width / tabsData.length),
            duration: 250,
            useNativeDriver: false,
          }).start();
        }}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    marginTop: 100, // Space for tab bar at the top
  },
  tabContainer: {
    width,
    justifyContent: 'center',
    alignItems: 'center',
  },
  textInput: {
    width: '80%',
    borderColor: '#ccc',
    borderWidth: 1,
    padding: 10,
    borderRadius: 5,
  },
  tabButton: {
    flex: 1,
    alignItems: 'center',
  },
  indicator: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    backgroundColor: 'blue',
    borderRadius: 10,
  },
});

export default CustomTabView;

Snack or a link to a repository

https://snack.expo.dev/_cgYFhPQvKDe1htoimwii

Reanimated version

3.16.1

React Native version

0.76

Platforms

Android

JavaScript runtime

None

Workflow

None

Architecture

None

Build type

None

Device

None

Device model

No response

Acknowledgements

Yes

@github-actions github-actions bot added Platform: Android This issue is specific to Android Repro provided A reproduction with a snippet of code, snack or repo is provided labels Nov 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Platform: Android This issue is specific to Android Repro provided A reproduction with a snippet of code, snack or repo is provided
Projects
None yet
Development

No branches or pull requests

1 participant