Skip to content

Expo SDK 52 upgrade breaks animations #561

@kristof-kovacs

Description

@kristof-kovacs

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:

  1. Drag and drop rearranging the values of the list works, but upon drop, the entries flash back to their original order for a second before settling in the final, correct positions. This make the rearranging feel and look extremely jittery.
  2. With the drag and drop, the item we are dragging sometimes does not resize back down to its normal size after being dropped, leading to an oversized item in the lift. Screenshot of this is attached.

Full code of the component is attached below.

IMG_3A7A57DB5DA1-1

Platform & Dependencies
Please list any applicable dependencies in addition to those below (react-navigation etc).

  • react-native-draggable-flatlist version: ^4.0.1
  • Platform: ios
  • React Native or Expo version: expo 52.0.4
  • Reanimated version: 3.16.1
  • React Native Gesture Handler version: ~2.20.2
  • React Native version: 0.76.1

Previous Platform & Dependencies where this code worked

  • react-native-draggable-flatlist version: ^4.0.1
  • Platform: ios
  • React Native or Expo version: expo 51.0.6
  • Reanimated version: ~3.10.1
  • React Native Gesture Handler version: ^2.16.1
  • React Native version: 0.74.1
    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>
    );
};`

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions