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.
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}
....
/>
);
}
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, 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), useuseMemowith function oruseValuefrom 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.
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, 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), useuseMemowith function oruseValuefrom Reanimated instead.