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 FlatList doesn't re-render correctly when last item deleted on Android #1748

Closed
helgahjartar opened this issue Feb 24, 2021 · 7 comments
Labels
Bug Platform: Android This issue is specific to Android 🛖 Reanimated 1

Comments

@helgahjartar
Copy link

helgahjartar commented Feb 24, 2021

Description

I'm trying to use an Animated FlatList using the libraries reanimated and gesture-handler from you guys. I have a horizontal list of items that have an option to be removed when you click an x in the corner.

This is working fine on iPhone, however on Android it seems the default scrolling when the last item in the list is removed stays put, displaying a non-existing empty item (leaving the component where the item was blank), instead of being re-rendered to the new last item in the updated array.

Code

export const useDisplayCards = () => {
   const card1 = useSelector(state => state.homeCards.card1);
   const card2 = useSelector(state => state.homeCards.card2);
   const card3 = useSelector(state => state.homeCards.card3);
   const card4 = useSelector(state => state.homeCards.card3);
   const currentUser = useSelector(state => state.user.currentUser);
   const dispatch = useDispatch();

   const [components, setComponents] = useState<Component[]>([
      {
         id: 'card1',
         comp: <View key="1" />,
         iconColor: COLORS.WHITE,
         seen: card1,
         showHideButton: false,
      },
      {
         id: 'card2',
         comp: <View key="2" />,
         iconColor: COLORS.DARK,
         seen: card2,
         showHideButton: false,
      },
      {
         id: 'card3',
         comp: <View key="3" />,
         iconColor: COLORS.DARK,
         seen: card3,
         showHideButton: true,
      },
      {
         id: 'card4',
         comp: <View key="4" />,
         iconColor: COLORS.WHITE,
         seen: card4,
         showHideButton: true,
      },
   ]);

   const scrollX = useValue(0);

   useEffect(() => {
      const filterNull = components.filter(el => el.seen && el);
      setComponents(filterNull);
   }, [currentUser, card1, card2, card3, card4]);

   const renderItems = ({ item, index }: { item: Component; index: number }) => {
      const inputRange = [
         (index - 1) * DIMENSIONS.WIDTH,
         index * DIMENSIONS.WIDTH,
         (index + 1) * DIMENSIONS.WIDTH,
      ];
      const scale = interpolateNode(scrollX, {
         inputRange,
         outputRange: [0.9, 1, 0.75],
      });
      return (
         <Animated.View style={[styles.flex, { transform: [{ scale }] }]}>
            <PaperCard
               showHideButton={item.showHideButton}
               style={{
                  marginTop: 30,
                  width: DIMENSIONS.WIDTH - 65,
               }}
               iconColor={item.iconColor || COLORS.DARK}
               hideCard={() => {
                  const updatedItems = components.filter((_item, itemIndex) => itemIndex !== index);
                  setComponents(updatedItems);
                  dispatch(hideCard(item.id));
               }}>
               {item.comp}
            </PaperCard>
         </Animated.View>
      );
   };

   const pagination = (data: Component[]) => {
      const inputRange = [-DIMENSIONS.WIDTH - 65, 0, DIMENSIONS.WIDTH - 65];
      const translateX = interpolateNode(scrollX, {
         inputRange,
         outputRange: [-40, 0, 40],
      });
      return (
         <View style={styles.pagination}>
            <Animated.View style={[styles.paginationDotActive, { transform: [{ translateX }] }]} />
            {data.map(item => {
               return (
                  <View key={item.id} style={styles.paginationContainer}>
                     <View style={styles.paginationDot} />
                  </View>
               );
            })}
         </View>
      );
   };

   return [components, scrollX, pagination, renderItems] as const;
};
____________________________
export type PaperCardProps = {
   style?: ViewStyle;
   navigate?(): void;
   children: ReactNode;
   hideCard?(): void;
   iconColor?: string;
   showHideButton?: boolean;
   onPress?(): void;
};
const PaperCard: FunctionComponent<PaperCardProps> = ({
   style,
   navigate,
   children,
   hideCard,
   iconColor,
   showHideButton = false,
   onPress,
}) => {
   return (
      <TouchableOpacity onPress={onPress} activeOpacity={1}>
         {showHideButton && (
            <IconButton
               style={styles.iconButton}
               onPress={hideCard}
               iconStyle={styles.iconStyle}
            />
         )}
         <TouchableOpacity onPress={navigate} style={[styles.container, style]} activeOpacity={1}>
            {children}
         </TouchableOpacity>
      </TouchableOpacity>
   );
};

export default PaperCard;
______________________________
export const hideCard = (card: string) => async (dispatch: AppDispatch) => {
   dispatch({
      type: HIDE_HOME_CARD,
      payload: { card },
   });
};
______________________________

const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);

const HomeScreen = FunctionComponent<> = () => {
  const dispatch = useDispatch();
  const currentUser = useSelector(state => state.user.currentUser);
  const [components, scrollX, pagination, renderItems] = useDisplayCards();

  const list = (
      <View style={styles.paperCardContainer}>
         <AnimatedFlatList
            showsHorizontalScrollIndicator={false}
            contentContainerStyle={{ paddingRight: 40, paddingLeft: 20 }}
            extraData={components}
            data={components}
            keyExtractor={(item: Component, index: number) => `${index} ${item}`}
            renderItem={renderItems}
            horizontal
            snapToInterval={DIMENSIONS.WIDTH - 65}
            decelerationRate="fast"
            onScroll={Animated.event([{ nativeEvent: { contentOffset: { x: scrollX } } }], {
               useNativeDriver: true,
            })}
            scrollEventThrottle={16}
         />
         {components.length > 1 ? pagination(components) : null}
      </View>
   );

 return (
            <ScrollBottomSheet
               ...
               ListHeaderComponent={list}
              ....
            />
 );
}

Steps to reproduce

Make an Animated FlatList with the option to delete items in the list.

Expected behaviour

List is updated by showing the new last item in the list instead of the deleted item

Actual behaviour

Place where item was deleted is just blank and you have to touch the screen so that the list updates

Package versions

  • React Native: 0.63.3
  • React Native Reanimated: 2.0.0-rc.3
  • React Native Gesture Handler : 1.7.0
@github-actions
Copy link

github-actions bot commented Feb 24, 2021

Issue validator

The issue is invalid!

  • Section required but not found: expected behavior(for label 🐞 bug)
  • Section required but not found: actual behavior & steps to reproduce(for label 🐞 bug)

@alexpchin
Copy link

I think I'm seeing a similar issue on iOS. I made a little progress adding a key to my Touchable but it's still not working 100%. Can you try to do that and see if it makes a difference?

@gudberg
Copy link

gudberg commented Mar 8, 2021

This is labelled as Reanimated 1 problem. But she is using Reanimated: 2.0.0-rc.3

@jakub-gonet
Copy link
Member

No, @helgifeiti is using const scrollX = useRef(new Animated.Value(0)).current; in their code so this is v1 issue.

@helgahjartar
Copy link
Author

@jakub-gonet I've updated the example code using v2 standards.

I've only found one issue on the internet which seems to be the same as this one: facebook/react-native#27206, makes me think maybe this is unrelated to the reanimated lib.

@jakub-gonet
Copy link
Member

But you still use interpolateNode and useValue - it's still v1 code if I'm not mistaken.

@helgahjartar
Copy link
Author

Closing this as this is not a react-native-reanimated issue, but an Android specific issue, same as is reported here: meliorence/react-native-snap-carousel#623

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Platform: Android This issue is specific to Android 🛖 Reanimated 1
Projects
None yet
Development

No branches or pull requests

4 participants