Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Animated.Value value after animation if component was re-mounted #24571

Closed
wants to merge 2 commits into from
Closed

Fix Animated.Value value after animation if component was re-mounted #24571

wants to merge 2 commits into from

Conversation

michalchudziak
Copy link
Contributor

Summary

Fixes #23712

Currently, It seems like __nativeAnimatedValueListener is not listening to the correct onAnimatedValueUpdate events if component was re-mounted. In this PR I'm attaching a new listener if the native view tag has changed.

Changelog

[General] [Fixed] - Fixed Animated.Value value after animation if component was re-mounted

Test Plan

Compare fresh react native app with this branch and current master. Following code from @karhatsu demonstrates issue very well:

App.js

import React from 'react';
import { Animated, Button, ScrollView, StyleSheet, Text, View } from 'react-native';

const containerWidth = 200
const boxSize = 50

export default class App extends React.Component {
  constructor(props) {
    super(props)
    this.x = new Animated.Value(0)
    this.valueChangeCounter = 0
    this.x.addListener(({ value }) => {
      // do not log every change since there are so many of them
      if (this.valueChangeCounter % 5 === 0) {
        this.log(`x: ${value}`)
      }
      this.valueChangeCounter += 1
    })
    this.state = {
      boxVisible: true,
      logs: []
    }
  }

  render() {
    const { boxVisible } = this.state
    const toggleText = boxVisible ? 'Hide' : 'Show'
    return (
      <View style={styles.container}>
        {this.renderBox()}
        <View style={styles.buttonsContainer}>
          <Button style={styles.button} title="<-" onPress={this.moveTo(0)} disabled={!boxVisible}/>
          <Button style={styles.button} title={toggleText} onPress={this.toggleVisibility}/>
          <Button style={styles.button} title="->" onPress={this.moveTo(containerWidth - boxSize)} disabled={!boxVisible}/>
        </View>
        {this.renderFix(false)}
        <Text style={styles.instructions}>
          Instructions: Click arrow buttons to move the box and see how the animated value changes during the animation.
          Then hide the box and reveal it again. After that the arrow button still moves the box in the UI but the
          underlying value does not change during the animation. This means that when you hide and show the box again,
          the box location does not reflect the latest animation end value.
        </Text>
        <ScrollView style={styles.logs}>
          {this.state.logs.map(log => {
            return <Text key={log.id}>{log.text}</Text>
          })}
        </ScrollView>
      </View>
    );
  }

  // call this with true and the bug gets "fixed"
  renderFix = (fixIt) => {
    if (fixIt) {
      return <Animated.View style={{ transform: [{ translateX: this.x }] }}/>
    }
  }

  renderBox = () => {
    if (this.state.boxVisible) {
      const horizontalLocation = { transform: [{ translateX: this.x }] }
      return (
        <View style={styles.boxContainer}>
          <Animated.View style={[styles.box, horizontalLocation]}/>
        </View>
      )
    } else {
      return (
        <View style={styles.boxContainer}>
          <Text>The box view is not being rendered</Text>
        </View>
      )
    }
  }

  moveTo = x => {
    return () => {
      this.log(`Animate to ${x}`)
      Animated.timing(this.x, {
        toValue: x,
        duration: 1000,
        useNativeDriver: true
      }).start(({ finished }) => {
        this.log(`Animation finished (${finished}), x is now ${this.x._value}`)
      })
    }
  }

  toggleVisibility = () => {
    const { boxVisible } = this.state
    this.setState({ boxVisible: !boxVisible })
    const log = `${boxVisible ? 'Hide box' : 'Show box'}, x is now ${this.x._value}`
    this.log(log)
  }

  log = text => {
    const logs = [...this.state.logs]
    logs.unshift({ id: logs.length, text })
    this.setState({ logs })
  }
}

const styles = StyleSheet.create({
  container: {
    display: 'flex',
    flexDirection: 'column',
    backgroundColor: '#fff',
    padding: 30,
  },
  boxContainer: {
    backgroundColor: 'yellow',
    height: boxSize,
    width: containerWidth,
  },
  box: {
    width: boxSize,
    height: boxSize,
    backgroundColor: 'orange',
  },
  buttonsContainer: {
    marginTop: 20,
    display: 'flex',
    flexDirection: 'row',
    justifyContent: 'space-between',
    width: containerWidth,
  },
  button: {
    width: 80,
  },
  instructions: {
    color: 'black',
    fontSize: 10,
    marginVertical: 20,
  },
  log: {
    color: 'gray',
  },
});

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 23, 2019
@cpojer
Copy link
Contributor

cpojer commented Apr 23, 2019

cc @janicduplessis could you review this PR?

Copy link
Contributor

@osdnk osdnk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michalchudziak It looks interesting. I was also trying to implement but didn't manage to find enough time. I'll happily see it merged.

However. Could you elaborate more on these changes? Looks quite tricky, why something like this should rely on invoking __getNativeTag method.

@michalchudziak
Copy link
Contributor Author

@osdnk I found out that after re-mounting the element, it's native-tag changes (which seems quite logical). The origin of this issue was the listener observing the view with the previous native-tag, not updating the listener if the tag changes. In this implementation, I set the flag if the native tag changes and update the listeners if this flag is set to true.

Copy link
Contributor

@janicduplessis janicduplessis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Thanks for figuring this out

Copy link
Contributor

@cpojer cpojer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's ship it.

Copy link
Contributor

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cpojer is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@react-native-bot
Copy link
Collaborator

This pull request was successfully merged by @michalchudziak in b3f7d53.

When will my fix make it into a release? | Upcoming Releases

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API: Animated Bug CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Animated.Value does not have correct value after animation if component was re-mounted
7 participants