react-native-reanimated: Animated.FlatList is broken
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.
Actual behavior & steps to reproduce
As you can see in the video, after deleting one element, the Pressable in next element is not pressable.
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)
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 12
- Comments: 18 (4 by maintainers)
This issue happens without
Animated.Flatlist
with a normalFlatList
I narrowed it down to usingAnimated.View
instead a normalFlatList
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
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.
I’m having the same issue after updating Expo SDK 43 > 44 which also updated reanimated from v2.2.x > 2.3.x.
When the
Animated.FlatList
data changes, there are left over “zombie” children from the previous data set or if thelistEmptyComponent
was rendered, it stays “on screen” but neither can be interacted with and both show as unmounted.This only seems to happen after I trigger any of the new API Layout methods (ie.
entering
,exiting
) anywhere in the app, it doesn’t have to be triggered from within the aforementioned FlatList or from within the same screen. Once I removed theentering
andexiting
props fromAnimated.View
the FlatList was working as intended.And as @Elabar mentioned, you need to restart the bundler to see the changes.
@Latropos you tested it in both Platforms? Because in iOS apparently is not working. I mean, the prop itemLayoutAnimation do nothing in mi case. I’ve the last version of Reanimated and it is my flatlist:
I’ve just tested it with the newest reanimated - everything seems to work correctly! 🥳 Thanks to everybody for submitting and commenting the issue.
Does anyone know if this issue still exists in reanimated 3? We’ve been blocked from using the entering/exiting/layout apis because of this issue and are wondering if investing the time to upgrade to one of the version 3 rc’s would be worth it.
I am experiencing the exact same thing as @jdlk07
Similar issue here with v2.3.0 + LayoutAnimation. After removing a flatlist entry and re-adding it (same key) after a couple renders, the touchable for that entry does not work.