Full code of the component is attached below.
const Favorites = ({ favoriteBooks, isLoading, navigation, onReorder, isCurrentUser, userDetails }) => {
const { theme } = useContext(ThemeContext);
const styles = getDynamicStyles(theme);
const [isEditMode, setIsEditMode] = useState(false);
const [data, setData] = useState(favoriteBooks);
useEffect(() => {
setData(favoriteBooks);
}, [favoriteBooks]);
const LibraryButton = () => (
<TouchableOpacity
onPress={() => navigation.navigate('Library', {
userDetails: userDetails
})}
style={[
styles.libraryButton,
{ opacity: isLoading ? 0.5 : 1 }
]}
disabled={isLoading}
>
<Ionicons
name="library-outline"
size={15}
color={theme.distinctAccentColor}
style={{ marginRight: 5 }}
/>
<Text style={[styles.libraryButtonText, { color: theme.distinctAccentColor }]}>
{isLoading ? 'Loading...' : 'View Library'}
</Text>
</TouchableOpacity>
);
const toggleEditMode = async () => {
if (isCurrentUser) {
if (isEditMode) {
// Exiting edit mode, save the new order
const newOrder = data.map(item => item.id);
try {
const result = await BookService.reorderFavorites(newOrder);
if (result) {
onReorder(newOrder);
setIsEditMode(false);
} else {
Alert.alert("Error", "Failed to save the new order. Please try again.");
}
} catch (error) {
console.error('Error saving favorites order:', error);
Alert.alert("Error", "An error occurred while saving. Please try again.");
}
} else {
// Entering edit mode
setIsEditMode(true);
}
}
};
useEffect(() => {
console.log("Setting navigation options. isEditMode:", isEditMode);
navigation.setOptions({
swipeEnabled: !isEditMode,
});
}, [isEditMode, navigation]);
const renderPlaceholderCard = () => {
return (
<View style={styles.placeholderCard}>
<Ionicons name="sparkles-outline" size={24} color={theme.subtleAccentColor} />
<Text style={styles.placeholderText}>Future Gem</Text>
</View>
);
};
const handleDragStart = useCallback(() => {
console.log("Drag started");
}, []);
const handleDragEnd = useCallback(({ data: newData, from, to }) => {
console.log("Drag ended. From:", from, "To:", to);
console.log("New data order:", newData.map(item => item.id));
setData(newData);
onReorder(newData.map(item => item.id));
}, [onReorder]);
const renderBookItem = useCallback(({ item, drag, isActive }) => {
if (item === 'placeholder') {
return renderPlaceholderCard();
}
if (isLoading) {
return (
<View style={styles.bookItem}>
<ShimmerPlaceholder
width={styles.thumbnail.width}
height={styles.thumbnail.height}
style={styles.thumbnail}
/>
<View style={[styles.bookTitle, { height: 16 }]} />
<View style={[styles.bookAuthor, { height: 12 }]} />
</View>
);
}
const book = item.book;
const navigateToBookDetails = () => {
if (!isEditMode) {
navigation.navigate('BookDetails', { book });
}
};
return (
<TouchableOpacity
onPress={navigateToBookDetails}
onLongPress={() => {
console.log("Long press detected on book:", book.title, "isEditMode:", isEditMode);
if (isEditMode) {
console.log("Initiating drag for book:", book.title);
drag();
}
}}
style={[
styles.bookItem,
isActive && styles.activeBookItem,
]}
>
<Image source={{ uri: book.cover_url }} style={styles.thumbnail} />
<Text allowFontScaling={false} style={styles.bookTitle}>{truncate(book.title, 15)}</Text>
<Text allowFontScaling={false} style={styles.bookAuthor}>{truncate(book.author, 15)}</Text>
</TouchableOpacity>
);
}, [isEditMode, isLoading, navigation, styles, theme]);
const favoritesList = isLoading
? [1, 2, 3, 4, 5]
: [...data];
while (favoritesList.length < 5) {
favoritesList.push('placeholder');
}
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<View style={styles.favoritesContainer}>
<View style={styles.favoritesHeaderContainer}>
<Text allowFontScaling={false} style={styles.favoritesHeader}>Favorites</Text>
{!isCurrentUser && <LibraryButton />}
{isCurrentUser && (
<TouchableOpacity onPress={toggleEditMode} style={styles.editButton}>
<Ionicons
name={isEditMode ? "save-outline" : "pencil-outline"}
size={20}
color={theme.mainTextColor}
/>
</TouchableOpacity>
)}
</View>
<DraggableFlatList
data={favoritesList}
renderItem={renderBookItem}
keyExtractor={(item, index) => {
if (isLoading) return `loading-${index}`;
if (item === 'placeholder') return `placeholder-${index}`;
return item.id ? item.id.toString() : `item-${index}`;
}}
horizontal
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
showsHorizontalScrollIndicator={false}
style={styles.favoritesFlatList}
contentContainerStyle={styles.favoritesContent}
ItemSeparatorComponent={() => <View style={styles.favoritesItemSeparator} />}
activationDistance={0}
dragItemOverflow={true}
dragHitSlop={{ top: 10, bottom: 10, left: 20, right: 20 }}
autoscrollThreshold={20}
autoscrollSpeed={100}
/>
<View style={styles.swipeGuideContainer}>
<Icon name="arrow-back" size={15} color={theme.subtleAccentColor} />
<Icon name="arrow-forward" size={15} color={theme.subtleAccentColor} />
</View>
</View>
</GestureHandlerRootView>
);
};`
Describe the bug
After an upgrade to Expo SDK version 52, and all relevant dependencies, a previously working horizontal draggable flatlist exhibits two incorrect behaviors:
Full code of the component is attached below.
Platform & Dependencies
Please list any applicable dependencies in addition to those below (react-navigation etc).
Previous Platform & Dependencies where this code worked