react / react/react-native

Change in 'key' prop leads to reset to original style/animated value after animating a view in 0.72

Đang mở
#38,510 4 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

API: Animated Issue: Author Provided Repro Needs: Triage :mag:
Ngôn ngữ chính
C++
Star
127k
Fork
25.3k
Merge trung bình
1 ngày 23 giờ
Pull request đã merge (30 ngày)
4

Mô tả

Description

After executing an animation (such as Animated.timing) with useNativeDriver set to true, changing the "key" prop (which causes a remount) in a child view, that utilizes the animated value, results in a reset to the view's original style. The updated Animated.Value is not being reflected in the UI.

This bug seems to be specific to version 0.72.

Note: This issue bears similarity to #34665, but the bug I am reporting only occurs in version 0.72 and exclusively upon changing the "key" prop (remounting vs replacing the style). This suggests potentially different root causes.

https://github.com/facebook/react-native/assets/5274340/c210afb1-d769-45cc-a012-f149f7f0334c

As you can see the view jumps back to its original position (after key change).

https://github.com/facebook/react-native/assets/5274340/ce0bd8ff-8c43-4d0d-b56e-0831ce6ef0d3

(Note: recorded in 0.70, but same result in 0.71)

React Native Version

0.72.3

Output of npx react-native info
System:
  OS: macOS 13.4.1
  CPU: (8) arm64 Apple M1 Pro
  Memory: 308.67 MB / 16.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 20.4.0
    path: /opt/homebrew/bin/node
  Yarn: Not Found
  npm:
    version: 9.7.2
    path: /opt/homebrew/bin/npm
  Watchman:
    version: 2023.07.10.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.12.1
    path: /opt/homebrew/bin/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 22.4
      - iOS 16.4
      - macOS 13.3
      - tvOS 16.4
      - watchOS 9.4
  Android SDK: Not Found
IDEs:
  Android Studio: 2021.1 AI-211.7628.21.2111.8193401
  Xcode:
    version: 14.3.1/14E300c
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 11.0.16.1
    path: /usr/bin/javac
  Ruby:
    version: 2.7.8
    path: /opt/homebrew/opt/ruby@2.7/bin/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 18.2.0
    wanted: 18.2.0
  react-native:
    installed: 0.72.3
    wanted: 0.72.3
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: Not found
  newArchEnabled: Not found
iOS:
  hermesEnabled: Not found
  newArchEnabled: Not found
Steps to reproduce
  1. Initialise a simple animation with Animated.Timing (useNativeDriver: true) from value 0 to 10.
  2. Create an Animated.View that employs a style which interpolates the animation value as needed.
  3. Set the "key" prop of the view to reference some part of the state.
  4. Upon completion of the animation, use the callback to modify the state (while keeping the Animated.Value constant) and thus the "key" prop of the Animated.View.

Expected result: The Animated.View should reflect the updated Animated.Value.

Actual result: In 0.72, The Animated.View reverts to its original display, ignoring the changes in Animated.Value. Inspecting the value with devtools confirms the value is indeed at 10, but the expected interpolated style is not being applied.

Snack, code example, screenshot, or link to a repository

https://snack.expo.dev/@kducros/trusting-banana

Note: As of time of posting, Snack has not been updated to Expo SDK 49 (with RN 0.72), so this shows expected behaviour.

The example below has a simple animation, and sets the state on completion, which is used in the "key" prop.

App.js:

import { useState, useRef } from 'react';
import { StyleSheet, Text, View, TouchableOpacity, Animated } from 'react-native';

const boxSize = 100;

export default function App() {
  
  const [hasAnimated, setHasAnimated] = useState(false);
  const animation = useRef(new Animated.Value(0)).current; 

  const resetExample = () => {
    animation.setValue(0);
    setHasAnimated(false);
  }
  
  const startBoxAnimation = () => {
    Animated.timing(animation, {
      toValue: 10,
      duration: 750,
      useNativeDriver: true
    }).start(() => {
      setHasAnimated(true);
    });
  }

  return (
    <View style={styles.container}>
      <View style={styles.buttons}>
        <TouchableOpacity disabled={hasAnimated} style={[styles.button, { opacity: hasAnimated ? 0.5 : 1}]} onPress={() => startBoxAnimation()}>
          <Text>Start</Text>
        </TouchableOpacity>
        <TouchableOpacity disabled={!hasAnimated} style={[styles.button, { opacity: !hasAnimated ? 0.5 : 1}]} onPress={() => resetExample()}>
          <Text>Reset</Text>
        </TouchableOpacity>
      </View>
      <Animated.View style={styles.boxFinalPosition}>
        {hasAnimated && <Text style={styles.textDesc}>Animated view should be at destination</Text>}
      </Animated.View>
      <Animated.View key={`box_${hasAnimated ? 'moved' : 'initial'}`} style={[styles.boxAnimated, {
        transform: [
          {
            translateY: animation.interpolate({
              inputRange: [0, 10],
              outputRange: [0, 150]
            })
          }
        ]
      }]}>
        <Animated.View style={[styles.boxInner, {
          opacity: animation.interpolate({
            inputRange: [0, 10],
            outputRange: [0, 1]
          })
        }]} />
      </Animated.View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  buttons: {
    position: 'absolute',
    bottom: 75,
    flexDirection: 'row', 
  },
  button: {
    backgroundColor: '#eee',
    padding: 20,
    margin: 2
  },
  boxAnimated: {
    position: 'absolute',
    top: 250,
    width: boxSize,
    height: boxSize,
    backgroundColor: 'red',
    overflow: 'hidden'
  },
  boxInner: {
    ...StyleSheet.absoluteFill,
    backgroundColor: 'blue',
  },
  boxFinalPosition: {
    position: 'absolute',
    top: 400,
    width: boxSize,
    height: boxSize,
    backgroundColor: '#eee',
    borderWidth: 3,
    borderStyle: 'dashed',
  },
  textDesc: {
    width: boxSize,
    height: boxSize,
    position: 'absolute',
    top: 120,
    color: '#000',
    textAlign: 'center',
    fontWeight: 'bold',
    fontSize: 10
  }
});

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu với việc tái hiện trong App.js, đặc biệt là Animated.timing, phép nội suy của Animated.Value và Animated.View có key thay đổi sau callback của animation. Tái hiện trên React Native 0.72.3 và truy vết lý do việc mount lại làm mất style animation đang được hiển thị. Hoàn tất có nghĩa là view vẫn giữ vị trí animation và độ mờ đã cập nhật sau khi key thay đổi.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
javascript, react-native
Lĩnh vực
mobile
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
35/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.