Description
I've made a selection object that keeps track of which element of the list is
selected (selection is made using presses). Elements are in the list but the
top list can be rerendered during the lifetime of the app (so can the
elements). If LayoutAnimation.easeInEaseOut() is used in the way below,
interaction with list gets glitchy.
Reproduction Steps and Sample Code
Selecting after the setTimeout event (when the top view App component
is rerendered) will result in duplicate child views, or even removals of some
child views.

import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
TouchableOpacity,
LayoutAnimation,
} from 'react-native';
class Selection {
id: ?string = null;
listeners: Map<string, (selected: boolean) => void> = new Map();
addListener = (id: string, f: (selected: boolean) => void) => {
this.listeners.set(id, f);
}
removeListener = (id: string) => {
this.listeners.delete(id);
}
setSelected = (id: string) => {
if (this.id && this.id !== id) {
const func = this.listeners.get(this.id) || function a() {};
func(false);
}
this.id = id;
const f = this.listeners.get(this.id) || function a() {};
f(true);
}
}
export default class App extends Component {
selection = new Selection();
state = { orders: [
{id: 'one',
status: 'onestatus',
},
{id: 'two',
status: 'twostatus',
},
{id: 'three',
status: 'threestatus',
},
{id: 'four',
status: 'fourstatus',
},
{id: 'five',
status: 'fivestatus',
},
{id: 'six',
status: 'sixstatus',
},
]};
componentWillUpdate = () => {
LayoutAnimation.easeInEaseOut();
}
modifyState = () => {
const copyArr = this.state.orders.slice();
copyArr[3].status = Math.random();
this.setState({ orders: copyArr });
}
render() {
setTimeout(this.modifyState, 5000);
return (
<View style={styles.container}>
{this.state.orders.map((v) => {
return (<WrapperComponent
key={v.id}
info={v.id}
status={v.status}
selection={this.selection}
/>);
})}
</View>)
}
}
class WrapperComponent extends React.Component {
state: {selected: boolean} = {selected: false};
shouldComponentUpdate = (nextProps, nextState) => {
return this.props.status !== nextProps.status ||
this.state.selected !== nextState.selected;
}
constructor(props: Props) {
super(props);
const selection = props.selection;
const info = props.info;
selection && selection.addListener(info, this.markAsSelected);
}
markAsSelected = (status: boolean) => {
this.setState({ selected: status });
}
componentWillUnmount = () => {
const selection = this.props.selection;
const info = this.props.info;
selection && selection.removeListener(info);
}
onPress = () => {
const selection = this.props.selection;
selection && selection.setSelected(this.props.info);
}
render() {
const selected = this.state.selected;
return (
<TouchableOpacity key={`${this.props.info}${selected}`} style={{ borderWidth: 5, borderColor: selected ? 'red' : 'black' }} onPress={this.onPress}>
<Text style={styles.instructions}>
{this.props.info}
</Text>
<Text style={styles.instructions}>
{this.props.status}
</Text>
<Text style={styles.instructions}>
{selected}
</Text>
</TouchableOpacity>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#F5FCFF',
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
AppRegistry.registerComponent('App', () => App);
Solution
Avoiding this bug can be accomplished by removing the easeInEaseOut call, or
by removing the key property in the TouchableOpacity.
Additional Information
- React Native version: 0.43.0-rc.4
- Platform: iOS
- Development Operating System: MacOS
- Dev tools: Xcode
Can reproduce on iOS react-native 0.42.3 with emulator and real device. The pressable element does not have to be TouchableOpacity - works with a variety of components.
The reason why the bug is happening is because the order of
insertReactSubview and removeReactSubview is incorrect. If easeInEaseOut
is removed then we can see that first removeReactSubview is called and then
insertReactSubview is called. If easeInEaseOut is present then
insertReactSubview will be called first twice and then removeReactSubview
twice (two elements changed, unselected list element turned into selected
one and vice versa). The problem is that removeReactSubview might have wrong
subview in arguments (attached .gif illustrates the duplication - one component on top the other - and removal - whole subview disappears).
Description
I've made a selection object that keeps track of which element of the list is
selected (selection is made using presses). Elements are in the list but the
top list can be rerendered during the lifetime of the app (so can the
elements). If
LayoutAnimation.easeInEaseOut()is used in the way below,interaction with list gets glitchy.
Reproduction Steps and Sample Code
Selecting after the

setTimeoutevent (when the top view App componentis rerendered) will result in duplicate child views, or even removals of some
child views.
Solution
Avoiding this bug can be accomplished by removing the
easeInEaseOutcall, orby removing the
keyproperty in theTouchableOpacity.Additional Information
Can reproduce on iOS react-native 0.42.3 with emulator and real device. The pressable element does not have to be
TouchableOpacity- works with a variety of components.The reason why the bug is happening is because the order of
insertReactSubviewandremoveReactSubviewis incorrect. IfeaseInEaseOutis removed then we can see that first
removeReactSubviewis called and theninsertReactSubviewis called. IfeaseInEaseOutis present theninsertReactSubviewwill be called first twice and thenremoveReactSubviewtwice (two elements changed, unselected list element turned into selected
one and vice versa). The problem is that
removeReactSubviewmight have wrongsubview in arguments (attached
.gifillustrates the duplication - one component on top the other - and removal - whole subview disappears).