React-native-reanimated: Animated FlatList doesn't re-render correctly when last item deleted on Android

Created on 30 Nov 2020  路  7Comments  路  Source: software-mansion/react-native-reanimated

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.

Wasn't sure whether to report this as a bug or not, but any help with this would be much appreciated.

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 = useRef(new Animated.Value(0)).current;

   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 = scrollX.interpolate({
         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 = scrollX.interpolate({
         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}
              ....
            />
 );
}

Package versions

  • React Native: 0.63.3
  • React Native Reanimated: 1.13.1
  • React Native Gesture Handler : 1.7.0
鉂換uestion

Most helpful comment

Could you please add a full (copy-pastable, without external deps) example so I can test this locally? You didn't attach dispatch() function, nor hideItem().

At the first glance, I see you're using useRef with Animated.Value, this won't work (you're creating a new instance of Value that is not used, but it initializes the native node), use useMemo with function or useValue from Reanimated instead.

All 7 comments

Issue validator - update # 8

Hello!
Congratulations! Your issue passed the validator! Thank you!

anyone?

Could you please add a full (copy-pastable, without external deps) example so I can test this locally? You didn't attach dispatch() function, nor hideItem().

At the first glance, I see you're using useRef with Animated.Value, this won't work (you're creating a new instance of Value that is not used, but it initializes the native node), use useMemo with function or useValue from Reanimated instead.

Could you please add a full (copy-pastable, without external deps) example so I can test this locally? You didn't attach dispatch() function, nor hideItem().

At the first glance, I see you're using useRef with Animated.Value, this won't work (you're creating a new instance of Value that is not used, but it initializes the native node), use useMemo with function or useValue from Reanimated instead.

Hi @jakub-gonet ,
I have updated the issue to include all relevant items. The dispatch functionality of hideCard is not relevant to this particular issue, it's being used to keep track of the state elsewhere.
As you can see, the list itself is used as a ListHeaderComponent inside a ScrollBottomSheet.

Please note that this is using v1 of the reanimated lib.

Let me know if you need anything else,
thank you!

Anyone had a chance to look at this yet?

Hi again, could anyone help with this please?

Hello and happy new year,

just wanted to bring attention to this issue as it is still unresolved. Any help would be much appreciated.

Was this page helpful?
0 / 5 - 0 ratings