question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Animated.FlatList is broken

See original GitHub issue

Description

Button in FlatList is not pressable after the element is unmounted. Kinda hard to explain verbally, please take a look at the video below. Interestingly, if you used Animated.FlatList before, and swap it back to regular FlatList, the regular FlatList will be affected as well. You need to restart the bundler to resolve the problem.

Expected behavior

Pressable in next element should be pressable after the previous element is removed.

https://user-images.githubusercontent.com/15872787/145156335-3af68ea6-167e-45b7-94df-438a03cf750c.mp4

Actual behavior & steps to reproduce

As you can see in the video, after deleting one element, the Pressable in next element is not pressable.

https://user-images.githubusercontent.com/15872787/145156207-1888ad3f-1da9-4378-b3ff-652bdd65c95a.mp4

Snack or minimal code example

diff between these implementation is within the FlatList only

React Native's FlatList implementation
import React, {useState} from 'react';
import {
  Button,
  View,
  Text,
  FlatList,
  TextInput,
  SafeAreaView,
} from 'react-native';

function Participant({name, onRemove, id}) {
  return (
    <View style={[styles.participantView]}>
      <Text>{`${name};${id}`}</Text>
      <Button title="Remove" color="red" onPress={onRemove} />
    </View>
  );
}

const App = () => {
  const [inputValue, setInputValue] = useState('Reanimated');
  const [participantList, setParticipantList] = useState([]);

  const addParticipant = () => {
    setParticipantList(
      [{name: inputValue, id: Date.now().toString()}].concat(participantList),
    );
  };

  const renderParticipant = React.useCallback(({item: participant}) => {
    const removeParticipant2 = () => {
      setParticipantList(prev => {
        return prev.filter(prevPar => prevPar.id !== participant.id);
      });
    };
    return (
      <Participant
        key={participant.id + 'FLATLIST'}
        id={participant.id}
        name={participant.name}
        onRemove={removeParticipant2}
      />
    );
  }, []);

  return (
    <SafeAreaView style={{flex: 1, width: '100%'}}>
      <View style={styles.listView}>
        <Text>FlatList</Text>
        <FlatList
          data={participantList}
          style={[{width: '100%'}]}
          renderItem={renderParticipant}
        />
      </View>

      <View style={[styles.bottomRow]}>
        <View style={[styles.textInput]}>
          <Text>Add participant: </Text>
          <TextInput
            placeholder="Name"
            value={inputValue}
            onChangeText={setInputValue}
          />
        </View>

        <Button
          title="Add"
          disabled={inputValue === ''}
          onPress={addParticipant}
        />
      </View>
    </SafeAreaView>
  );
};

const styles = {
  participantView: {
    borderBottomColor: 'black',
    flex: 1,
    borderBottomWidth: 1,
    padding: 10,
    display: 'flex',
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    backgroundColor: '#fffbeb',
  },
  listView: {
    flex: 1,
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'space-between',
    width: '100%',
  },
  bottomRow: {
    width: '100%',
    display: 'flex',
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: 10,
  },
  textInput: {
    display: 'flex',
    flexDirection: 'row',
    alignItems: 'center',
  },
};

export default App;

Problematic Animated.FlatList implementation
import React, {useState} from 'react';
import {Button, View, Text, TextInput, SafeAreaView} from 'react-native';
import Animated, {
  Layout,
  LightSpeedInLeft,
  LightSpeedOutLeft,
} from 'react-native-reanimated';

function Participant({name, onRemove, id}) {
  return (
    <Animated.View
      style={[styles.participantView]}
      entering={LightSpeedInLeft}
      exiting={LightSpeedOutLeft}
      layout={Layout.springify()}>
      <Text>{`${name};${id}`}</Text>
      <Button title="Remove" color="red" onPress={onRemove} />
    </Animated.View>
  );
}

const App = () => {
  const [inputValue, setInputValue] = useState('Reanimated');
  const [participantList, setParticipantList] = useState([]);

  const addParticipant = () => {
    setParticipantList(
      [{name: inputValue, id: Date.now().toString()}].concat(participantList),
    );
  };

  const renderParticipant = React.useCallback(({item: participant}) => {
    const removeParticipant2 = () => {
      setParticipantList(prev => {
        return prev.filter(prevPar => prevPar.id !== participant.id);
      });
    };
    return (
      <Participant
        key={participant.id + 'FLATLIST'}
        id={participant.id}
        name={participant.name}
        onRemove={removeParticipant2}
      />
    );
  }, []);

  return (
    <SafeAreaView style={{flex: 1, width: '100%'}}>
      <View style={styles.listView}>
        <Text>Animated.FlatList</Text>
        <Animated.FlatList
          itemLayoutAnimation={Layout.springify()}
          data={participantList}
          style={[{width: '100%'}]}
          renderItem={renderParticipant}
        />
      </View>

      <View style={[styles.bottomRow]}>
        <View style={[styles.textInput]}>
          <Text>Add participant: </Text>
          <TextInput
            placeholder="Name"
            value={inputValue}
            onChangeText={setInputValue}
          />
        </View>

        <Button
          title="Add"
          disabled={inputValue === ''}
          onPress={addParticipant}
        />
      </View>
    </SafeAreaView>
  );
};

const styles = {
  participantView: {
    borderBottomColor: 'black',
    flex: 1,
    borderBottomWidth: 1,
    padding: 10,
    display: 'flex',
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    backgroundColor: '#fffbeb',
  },
  listView: {
    flex: 1,
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'space-between',
    width: '100%',
  },
  bottomRow: {
    width: '100%',
    display: 'flex',
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: 10,
  },
  textInput: {
    display: 'flex',
    flexDirection: 'row',
    alignItems: 'center',
  },
};

export default App;

Package versions

  • React Native: 0.66.3
  • React Native Reanimated: 2.3
  • NodeJS:
  • Xcode:
  • Java & Gradle:

Affected platforms

  • Android
  • iOS (not sure)
  • Web (not sure)

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:11
  • Comments:15 (2 by maintainers)

github_iconTop GitHub Comments

3reactions
Stevemoretzcommented, Feb 22, 2022

This issue happens without Animated.Flatlist with a normal FlatList I narrowed it down to using Animated.View instead a normal FlatList

import * as SplashScreen from "expo-splash-screen";
import React, {useEffect, useState} from "react";
import {Button, FlatList, SafeAreaView, Text, TextInput, View} from "react-native";
import Animated from "react-native-reanimated";

function Participant({name, onRemove, id}) {
    return (
        <Animated.View
            style={[styles.participantView]}
            entering={LightSpeedInLeft}
            // exiting={LightSpeedOutLeft}
            // layout={Layout.springify()}
        >
            <Text>{`${name};${id}`}</Text>
            <Button title="Remove" color="red" onPress={onRemove} />
        </Animated.View>
    );
}

const App = () => {
    const [inputValue, setInputValue] = useState("Reanimated");
    const [participantList, setParticipantList] = useState([]);

    const addParticipant = () => {
        setParticipantList(
            [{name: inputValue, id: Date.now().toString()}].concat(
                participantList
            )
        );
    };

    const renderParticipant = React.useCallback(({item: participant}) => {
        const removeParticipant2 = () => {
            console.log("s");
            setParticipantList((prev) => {
                return prev.filter((prevPar) => prevPar.id !== participant.id);
            });
        };
        return (
            <Participant
                key={participant.id + "FLATLIST"}
                id={participant.id}
                name={participant.name}
                onRemove={removeParticipant2}
            />
        );
    }, []);

    useEffect(() => {
        SplashScreen.hideAsync();
    }, []);

    return (
        <SafeAreaView style={{flex: 1, width: "100%", backgroundColor: "red"}}>
            <View style={styles.listView}>
                <Text>Animated.FlatList</Text>
                <FlatList
                    // itemLayoutAnimation={Layout.springify()}
                    data={participantList}
                    style={[{width: "100%"}]}
                    renderItem={renderParticipant}
                />
            </View>

            <View style={[styles.bottomRow]}>
                <View style={[styles.textInput]}>
                    <Text>Add participant: </Text>
                    <TextInput
                        placeholder="Name"
                        value={inputValue}
                        onChangeText={setInputValue}
                    />
                </View>

                <Button
                    title="Add"
                    disabled={inputValue === ""}
                    onPress={addParticipant}
                />
            </View>
        </SafeAreaView>
    );
};

const styles = {
    participantView: {
        borderBottomColor: "black",
        flex: 1,
        borderBottomWidth: 1,
        padding: 10,
        display: "flex",
        flexDirection: "row",
        alignItems: "center",
        justifyContent: "space-between",
        backgroundColor: "#fffbeb",
    },
    listView: {
        flex: 1,
        flexDirection: "column",
        alignItems: "center",
        justifyContent: "space-between",
        width: "100%",
    },
    bottomRow: {
        width: "100%",
        display: "flex",
        flexDirection: "row",
        justifyContent: "space-between",
        alignItems: "center",
        padding: 10,
    },
    textInput: {
        display: "flex",
        flexDirection: "row",
        alignItems: "center",
    },
};

export default App;

So it doesn’t have anything to do with Animated.Flatlist and even layout prop.

Also tested on iOS and it works fine there.

just removing the entering makes it also work on android.

Created another issue with minimal example feel free to test : https://github.com/software-mansion/react-native-reanimated/issues/3029

2reactions
Guuzcommented, Feb 14, 2022

This even happens when i just use an Animated.View with one of the default layoutanimations anywhere on my screen. As soon as i do that “ghost” views stick around on Android. Like the ListEmptyComponent for instance.

Read more comments on GitHub >

github_iconTop Results From Across the Web

react native flatlist animation is not working as expected on ...
I'm working in app that needs flatlist animation with RTL devices. I built a small app to understand how it will work.
Read more >
Animated FlatList in React Native (Reanimated) - YouTube
What's up mobile devs? Today we're going to build from scratch a super powerful and simple animation over the React Native FlatList ......
Read more >
FlatList Animations in React Native with Reanimated 2
In this tutorial, we will implement FlatList animations in React Native, inspired by iOS 16 notifications, using the library Reanimated 2.
Read more >
FlatList - React Native
scrollToIndex() ​ · 'animated' (boolean) - Whether the list should do an animation while scrolling. · 'index' (number) - The index to scroll...
Read more >
React Native FlatList - Getting Your React Native Basics Right
import * as React from 'react'; import { FlatList, Text, View, StyleSheet } from 'react-native'; # A component to render individual item const ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found