react-native-draggable-flatlist icon indicating copy to clipboard operation
react-native-draggable-flatlist copied to clipboard

Expo SDK 52 upgrade breaks animations

Open kristof-kovacs opened this issue 1 year ago • 34 comments

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>
    );
};`

kristof-kovacs avatar Nov 18 '24 16:11 kristof-kovacs

Same here. After upgrading to Expo SDK 52 and rn reanimated to 3.16.1 (as required per Expo), experiencing the identical issue. It's reproducible in a fresh new barebones project. Also, react native's LayoutAnimation seems to have no effect also after the upgrade (I'm rearranging by list upon a button click)

hodirt avatar Nov 19 '24 08:11 hodirt

This mainly because of new arch, you can set newArchEnabled to false so the animation works normally

ghuyphan avatar Nov 21 '24 06:11 ghuyphan

Unfortunately, that does not solve it @shorkyyy. It was the first thing I tried when I ran into the issue and I just retried it again. I ensured that I ran a clean build by resetting everything, and both issues I described above still persist.

kristof-kovacs avatar Nov 21 '24 15:11 kristof-kovacs

Same problem here.

"react-native-draggable-flatlist": "^4.0.1", "react-native": "^0.76.1", "react-native-reanimated": "^3.6.0",

CRH-Champ avatar Nov 22 '24 07:11 CRH-Champ

Same problem here.

"react-native-draggable-flatlist": "^4.0.1", "react-native": "0.76.2", "react-native-reanimated": "~3.16.1",

0.5x

https://github.com/user-attachments/assets/69121f5c-8ebb-44e7-8336-5b071ba5938d

Unuuuuu avatar Nov 22 '24 09:11 Unuuuuu

+1 for the video @Unuuuuu, this is exactly what I am seeing as well

kristof-kovacs avatar Nov 22 '24 16:11 kristof-kovacs

Also I'm seeing a lot of console errors during the dragging.

(NOBRIDGE) WARN  [Reanimated] Tried to modify key `current` of an object which has been already passed to a worklet. See
https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#tried-to-modify-key-of-an-object-which-has-been-converted-to-a-shareable
for more details.

fonodi avatar Nov 24 '24 01:11 fonodi

Yes @fonodi I am seeing the same. You can silence these warnings for the meantime, but I bet they are somehow involved in this behavior

kristof-kovacs avatar Nov 25 '24 14:11 kristof-kovacs

Unfortunately, that does not solve it @shorkyyy. It was the first thing I tried when I ran into the issue and I just retried it again. I ensured that I ran a clean build by resetting everything, and both issues I described above still persist.

Haven't tested on sdk52 yet but I have tested it on sdk51 and when I enable newarch, the same issue appears and I had no clue what's wrong with it, so I cloned my project and set newarch = false and the issue disappears

ghuyphan avatar Nov 26 '24 07:11 ghuyphan

I have similar glitches in the same situation on sdk 52 with the new architecture enabled. When I disable the new architecture it works much better but still I get the same messages as @fonodi :

Also I'm seeing a lot of console errors during the dragging.

(NOBRIDGE) WARN  [Reanimated] Tried to modify key `current` of an object which has been already passed to a worklet. See
https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#tried-to-modify-key-of-an-object-which-has-been-converted-to-a-shareable
for more details.

BenGroot avatar Nov 27 '24 07:11 BenGroot

I am also having issues since upgrading to EXPO SDK 52.

This setup doesnt work anymore. The animations are broken and I am getting this error:

(NOBRIDGE) ERROR Warning: ref.measureLayout must be called with a ref to a native component.

MeNoKVN avatar Nov 28 '24 12:11 MeNoKVN

Managed to fix the same issue in my project with this patch, please check if it helps.

react-native-draggable-flatlist+4.0.1.patch.txt

ddrozdov avatar Dec 02 '24 15:12 ddrozdov

@MeNoKVN

(NOBRIDGE) ERROR Warning: ref.measureLayout must be called with a ref to a native component.

See #544

ddrozdov avatar Dec 02 '24 15:12 ddrozdov

Managed to fix the same issue in my project with this patch, please check if it helps.

react-native-draggable-flatlist+4.0.1.patch.txt

I applied your patch, now It's not so horrible, but I still can see some glitches...

aleksey-mukho avatar Dec 02 '24 21:12 aleksey-mukho

@ddrozdov thank you for the patch! I went ahead and applied it and as @aleksey-mukho noted, there has been an improvement but not all the way.

In detail: The Good: The items flashing back to their original spot is indeed fixed by your patch. This is great step forward.

The Bad: However, now the items themselves very briefly flash on rearrange. Sometimes multiple items, not just the ones being dragged. To me this suggests that the draggable flatlist is being re-rendered.

The Ugly: The resizing issue is still present, and I still cannot reproduce it consistently. Sometimes I will drag an item and it will drop down and resize down correctly. Other times, I will drag and drop, but the item never resizes down again.

kristof-kovacs avatar Dec 03 '24 15:12 kristof-kovacs

Hi @computerjazz Your project is amazing, but I think everyone is missing you 😃 Could you tell us what your plans are with the project?

BenGroot avatar Dec 11 '24 08:12 BenGroot

Hi @computerjazz Your project is amazing, but I think everyone is missing you 😃 Could you tell us what your plans are with the project?

He mentioned his plans here https://github.com/computerjazz/react-native-draggable-flatlist/issues/542#issuecomment-2246473428

fonodi avatar Dec 11 '24 10:12 fonodi

Has anyone managed to solve the problem?

Fjort avatar Dec 14 '24 08:12 Fjort

I have identified the cause of the problem. In the CellRenderer Component, the transform style is updated while the element is being moved. After changing the data, new data comes to onDragEnd, but the elements that match the offset keys still have the transform style, which is then reset to 0. I have not been able to solve this problem yet, but I continue to figure it out.

Fjort avatar Dec 14 '24 15:12 Fjort

The Bad: However, now the items themselves very briefly flash on rearrange. Sometimes multiple items, not just the ones being dragged. To me this suggests that the draggable flatlist is being re-rendered.

@kristof-kovacs I just noticed the same when in onDragEnd I updated my state with exactly the data I got from the library, but if I do data.map(item => ({ ...item })) the flash goes away. No explanation why, it just works for me :)

ddrozdov avatar Dec 16 '24 15:12 ddrozdov

@kristof-kovacs I just noticed the same when in onDragEnd I updated my state with exactly the data I got from the library, but if I do data.map(item => ({ ...item })) the flash goes away. No explanation why, it just works for me :)

It didn't help me ((

Fjort avatar Dec 16 '24 16:12 Fjort

@ddrozdov this actually helped for me as well for one moment, but then it reverted to the same behavior. So unfortunately not this did not fix it.

However, I still am running into the issue of some of the items that I move are resized wrong and do not size down after drag

kristof-kovacs avatar Dec 20 '24 17:12 kristof-kovacs

I also have the same problem. Even in the example, empty app. Should I try downgrading the Expo version?

Hormold avatar Dec 24 '24 03:12 Hormold

Bumping this thread to see if anybody has been able to find a solution, or perhaps an alternative package. I saw @harveyappleton was working on a fork for example.

kristof-kovacs avatar Jan 08 '25 01:01 kristof-kovacs

@kristof-kovacs I found this library https://github.com/omahili/react-native-reorderable-list. It worked nicely for me, without any glitches

Alex1899 avatar Jan 09 '25 19:01 Alex1899

For me what worked was installing this PR: https://github.com/computerjazz/react-native-draggable-flatlist/pull/551, Doing this comment: https://github.com/computerjazz/react-native-draggable-flatlist/issues/561#issuecomment-2545878644, And applied this patch: https://github.com/computerjazz/react-native-draggable-flatlist/issues/561#issuecomment-2511831441

Maybe one or all of those did the trick.

JustinRohweller avatar Jan 14 '25 17:01 JustinRohweller

@ddrozdov provided a patch that worked on my recent React Native project (without expo). However it doesn't work for me in other projects using Expo SDK 52.0 anymore.

milansoap avatar Jan 15 '25 18:01 milansoap

@Alex1899 that looks like a great alternative actually! Unfortunately it does not support horizontal scrolling, which is what or project requires.

kristof-kovacs avatar Jan 16 '25 13:01 kristof-kovacs

I have similar glitches in the same situation on sdk 52 with the new architecture enabled. When I disable the new architecture it works much better but still I get the same messages as @fonodi :

Also I'm seeing a lot of console errors during the dragging.

(NOBRIDGE) WARN  [Reanimated] Tried to modify key `current` of an object which has been already passed to a worklet. See
https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#tried-to-modify-key-of-an-object-which-has-been-converted-to-a-shareable
for more details.

Getting this issue on Expo 52,

marvin-rao avatar Feb 14 '25 09:02 marvin-rao

Managed to fix the same issue in my project with this patch, please check if it helps. react-native-draggable-flatlist+4.0.1.patch.txt

I applied your patch, now It's not so horrible, but I still can see some glitches...

Thx for a patch works like a charm 🚀

hlus avatar Mar 26 '25 18:03 hlus