Animated FlatList doesn't re-render correctly when last item deleted on Android
See original GitHub issueDescription
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 = 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}
....
/>
);
}
Package versions
- React Native: 0.63.3
- React Native Reanimated: 1.13.1
- React Native Gesture Handler : 1.7.0
Issue Analytics
- State:
- Created 3 years ago
- Reactions:2
- Comments:8 (1 by maintainers)
Top Results From Across the Web
Animated FlatList doesn't re-render correctly when last item ...
This is working fine on iPhone, however on Android it seems the default scrolling when the last item in the list is removed...
Read more >React Native FlatList last item visibility issue - Stack Overflow
We embed the FlatList component within our native UIs inside a Fragment in Android and we were unable to scroll to the last...
Read more >How to use the FlatList Component — React Native Basics
What is the FlatList component? It's an easy way to make an efficient scrolling list of data. Not only is it efficient but...
Read more >Optimizing React Native performance - LogRocket Blog
The FlatList component renders only the items that will be displayed on the screen and removes them when they are no longer displayed....
Read more >Optimizing Flatlist Configuration - React Native
The more complex your components are, the slower they will render. Try to avoid a lot of logic and nesting in your list...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Could you please add a full (copy-pastable, without external deps) example so I can test this locally? You didn’t attach
dispatch()
function, norhideItem()
.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), useuseMemo
with function oruseValue
from Reanimated instead.Just updated to Reanimated v2, but the problem still persists.