{"action":"created","issue":{"url":"https://api.github.com/repos/facebook/react-native/issues/13207","repository_url":"https://api.github.com/repos/facebook/react-native","labels_url":"https://api.github.com/repos/facebook/react-native/issues/13207/labels{/name}","comments_url":"https://api.github.com/repos/facebook/react-native/issues/13207/comments","events_url":"https://api.github.com/repos/facebook/react-native/issues/13207/events","html_url":"https://github.com/facebook/react-native/issues/13207","id":218029638,"number":13207,"title":"LayoutAnimation easeInEaseOut causes children of top view to disappear or duplicate","user":{"login":"vjeranc","id":4954310,"avatar_url":"https://avatars2.githubusercontent.com/u/4954310?v=4","gravatar_id":"","url":"https://api.github.com/users/vjeranc","html_url":"https://github.com/vjeranc","followers_url":"https://api.github.com/users/vjeranc/followers","following_url":"https://api.github.com/users/vjeranc/following{/other_user}","gists_url":"https://api.github.com/users/vjeranc/gists{/gist_id}","starred_url":"https://api.github.com/users/vjeranc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vjeranc/subscriptions","organizations_url":"https://api.github.com/users/vjeranc/orgs","repos_url":"https://api.github.com/users/vjeranc/repos","events_url":"https://api.github.com/users/vjeranc/events{/privacy}","received_events_url":"https://api.github.com/users/vjeranc/received_events","type":"User","site_admin":false},"labels":[{"id":718241807,"url":"https://api.github.com/repos/facebook/react-native/labels/Stale","name":"Stale","color":"e99695","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":7,"created_at":"2017-03-29T22:35:51Z","updated_at":"2017-11-26T08:50:54Z","closed_at":null,"author_association":"CONTRIBUTOR","body":"### Description\r\n\r\nI've made a selection object that keeps track of which element of the list is\r\nselected (selection is made using presses). Elements are in the list but the\r\ntop list can be rerendered during the lifetime of the app (so can the\r\nelements). If `LayoutAnimation.easeInEaseOut()` is used in the way below,\r\ninteraction with list gets glitchy.\r\n\r\n### Reproduction Steps and Sample Code\r\n\r\nSelecting after the `setTimeout` event (when the top view App component\r\nis rerendered) will result in duplicate child views, or even removals of some\r\nchild views.\r\n![bughappens](https://cloud.githubusercontent.com/assets/4954310/24479668/a7812d1c-14e0-11e7-946a-d6e69b6953af.gif)\r\n\r\n```javascript\r\nimport React, { Component } from 'react';\r\nimport {\r\n  AppRegistry,\r\n  StyleSheet,\r\n  Text,\r\n  View,\r\n  TouchableOpacity,\r\n  LayoutAnimation,\r\n} from 'react-native';\r\n\r\nclass Selection {\r\n\r\n  id: ?string = null;\r\n  listeners: Map<string, (selected: boolean) => void> = new Map();\r\n\r\n  addListener = (id: string, f: (selected: boolean) => void) => {\r\n    this.listeners.set(id, f);\r\n  }\r\n\r\n  removeListener = (id: string) => {\r\n    this.listeners.delete(id);\r\n  }\r\n\r\n  setSelected = (id: string) => {\r\n    if (this.id && this.id !== id) {\r\n      const func = this.listeners.get(this.id) || function a() {};\r\n      func(false);\r\n    }\r\n    this.id = id;\r\n    const f = this.listeners.get(this.id) || function a() {};\r\n    f(true);\r\n  }\r\n}\r\n\r\nexport default class App extends Component {\r\n\r\n  selection = new Selection();\r\n\r\n  state = { orders: [\r\n    {id: 'one',\r\n     status: 'onestatus',\r\n    },\r\n    {id: 'two',\r\n     status: 'twostatus',\r\n    },\r\n    {id: 'three',\r\n     status: 'threestatus',\r\n    },\r\n    {id: 'four',\r\n     status: 'fourstatus',\r\n    },\r\n    {id: 'five',\r\n     status: 'fivestatus',\r\n    },\r\n    {id: 'six',\r\n     status: 'sixstatus',\r\n    },\r\n  ]};\r\n\r\n  componentWillUpdate = () => {\r\n    LayoutAnimation.easeInEaseOut();\r\n  }\r\n\r\n  modifyState = () => {\r\n    const copyArr = this.state.orders.slice();\r\n    copyArr[3].status = Math.random();\r\n    this.setState({ orders: copyArr });\r\n  }\r\n\r\n  render() {\r\n    \r\n    setTimeout(this.modifyState, 5000);\r\n    return (\r\n    <View style={styles.container}>\r\n      {this.state.orders.map((v) => {\r\n        return (<WrapperComponent\r\n          key={v.id}\r\n          info={v.id}\r\n          status={v.status}\r\n          selection={this.selection}\r\n        />);\r\n      })}\r\n    </View>)\r\n  }\r\n}\r\n\r\nclass WrapperComponent extends React.Component {\r\n\r\n  state: {selected: boolean} = {selected: false};\r\n\r\n  shouldComponentUpdate = (nextProps, nextState) => {\r\n    return this.props.status !== nextProps.status ||\r\n           this.state.selected !== nextState.selected;\r\n  }\r\n\r\n  constructor(props: Props) {\r\n    super(props);\r\n    const selection = props.selection;\r\n    const info = props.info;\r\n    selection && selection.addListener(info, this.markAsSelected);\r\n  }\r\n\r\n  markAsSelected = (status: boolean) => {\r\n    this.setState({ selected: status });\r\n  }\r\n\r\n  componentWillUnmount = () => {\r\n    const selection = this.props.selection;\r\n    const info = this.props.info;\r\n    selection && selection.removeListener(info);\r\n  }\r\n\r\n  onPress = () => {\r\n    const selection = this.props.selection;\r\n    selection && selection.setSelected(this.props.info);\r\n  }\r\n\r\n  render() {\r\n    \r\n    const selected = this.state.selected;\r\n    return (\r\n      <TouchableOpacity key={`${this.props.info}${selected}`} style={{ borderWidth: 5, borderColor: selected ? 'red' : 'black' }} onPress={this.onPress}>\r\n        <Text style={styles.instructions}>\r\n          {this.props.info}\r\n        </Text>\r\n        <Text style={styles.instructions}>\r\n          {this.props.status}\r\n        </Text>\r\n        <Text style={styles.instructions}>\r\n          {selected}\r\n        </Text>\r\n      </TouchableOpacity>\r\n    );\r\n  }\r\n}\r\n\r\nconst styles = StyleSheet.create({\r\n  container: {\r\n    flex: 1,\r\n    justifyContent: 'center',\r\n    backgroundColor: '#F5FCFF',\r\n  },\r\n  instructions: {\r\n    textAlign: 'center',\r\n    color: '#333333',\r\n    marginBottom: 5,\r\n  },\r\n});\r\n\r\nAppRegistry.registerComponent('App', () => App);\r\n```\r\n\r\n### Solution\r\n\r\nAvoiding this bug can be accomplished by removing the `easeInEaseOut` call, or\r\nby removing the `key` property in the `TouchableOpacity`.\r\n\r\n### Additional Information\r\n\r\n* React Native version: 0.43.0-rc.4\r\n* Platform: iOS\r\n* Development Operating System: MacOS\r\n* Dev tools: Xcode\r\n\r\nCan 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.\r\n\r\nThe reason why the bug is happening is because the order of\r\n`insertReactSubview` and `removeReactSubview` is incorrect. If `easeInEaseOut`\r\nis removed then we can see that first `removeReactSubview` is called and then\r\n`insertReactSubview` is called. If `easeInEaseOut` is present then\r\n`insertReactSubview` will be called first twice and then `removeReactSubview`\r\ntwice (two elements changed, unselected list element turned into selected\r\none and vice versa). The problem is that `removeReactSubview` might have wrong\r\nsubview in arguments (attached `.gif` illustrates the duplication - one component on top the other - and removal - whole subview disappears). "},"comment":{"url":"https://api.github.com/repos/facebook/react-native/issues/comments/346993409","html_url":"https://github.com/facebook/react-native/issues/13207#issuecomment-346993409","issue_url":"https://api.github.com/repos/facebook/react-native/issues/13207","id":346993409,"user":{"login":"stale[bot]","id":26384082,"avatar_url":"https://avatars2.githubusercontent.com/u/26350515?v=4","gravatar_id":"","url":"https://api.github.com/users/stale%5Bbot%5D","html_url":"https://github.com/apps/stale","followers_url":"https://api.github.com/users/stale%5Bbot%5D/followers","following_url":"https://api.github.com/users/stale%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/stale%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/stale%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stale%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/stale%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/stale%5Bbot%5D/repos","events_url":"https://api.github.com/users/stale%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/stale%5Bbot%5D/received_events","type":"Bot","site_admin":false},"created_at":"2017-11-26T08:50:54Z","updated_at":"2017-11-26T08:50:54Z","author_association":"NONE","body":"This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Maybe the issue has been fixed in a recent release, or perhaps it is not affecting a lot of people. If you think this issue should definitely remain open, please let us know why. Thank you for your contributions.\n"}}
{"actor":{"display_login":"stale"}}