software-mansion / react-native-reanimated

React Native's Animated library reimplemented
https://docs.swmansion.com/react-native-reanimated/
MIT License
8.85k stars 1.29k forks source link

Changing variables in a component's scope with Fast Refresh breaks `useDerivedValue` #1964

Closed nandorojo closed 9 months ago

nandorojo commented 3 years ago

Description

If you remove a prop from a component that has a derived value, and save, then the component's derived value no longer responds to shared value changes.

Once I do a hard refresh, everything works fine.

Expected behavior

Changing the props that don't affect a derived value with fast refresh shouldn't break the derived value.

Actual behavior & steps to reproduce

I know the wording is a little confusing for this issue, so I made a screen recording to demonstrate it: https://www.loom.com/share/91bc5f2a6d074d209149e2531d9ff8f3

When using Fast Refresh, if I add / remove a prop to a component, and then save, a derived value in the component receiving props becomes stale and no longer updates in response to shared value changes.

Snack or minimal code example

Reproduction: https://github.com/nandorojo/reanimated-refresh-bug

Code here ```jsx import * as React from 'react' import { Text, View, StyleSheet, Pressable } from 'react-native' import Animated, { useSharedValue, useDerivedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated' /** * Inside of App, try commenting out the extraState prop that is passed to to * * Once you comment it out, and save with Fast Refresh, the derived value no longer updates when you press on the box. * * Updating the scoped variables around a derived value seems to break the mutation of a shared value */ function Shape({ extraState, pressed }) { const opacity = useDerivedValue(() => (pressed.value ? 0.5 : 1)) const style = useAnimatedStyle(() => ({ opacity: withTiming(opacity.value) })) return } export default function App() { const pressed = useSharedValue(false) const [pressedState, setPressed] = React.useState(false) const onPressIn = () => { pressed.value = true setPressed(true) } const onPressOut = () => { pressed.value = false setPressed(false) } return ( ) } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: 'cyan', padding: 8 }, shape: { height: 200, width: 200, backgroundColor: 'black', borderRadius: 16 } }) ```

Package versions

Affected platforms

github-actions[bot] commented 3 years ago

Issue validator

The issue is valid!

nandorojo commented 3 years ago

I tried adding a dependency array to useDerivedValue, but this had no impact.

thisisgit commented 2 years ago

I'm facing same issue here. I think it has something to do with re-render happening asynchronously. Here's my reproducible code:

import React, { useReducer } from 'react';
import { Pressable, StyleSheet, Text } from 'react-native';
import { runOnJS, useAnimatedReaction, useDerivedValue, useSharedValue } from 'react-native-reanimated';

export const Test = () => {
  const sharedValue = useSharedValue<number>(0);
  const derivedValue = useDerivedValue<number>(() => {
    console.log('shared value changed', sharedValue.value);
    return sharedValue.value + 5;
  });
  const [_, forceReRender] = useReducer(x => x + 1, 0);

  useAnimatedReaction(
    () => {
      console.log('derived value changed', derivedValue.value);
      return derivedValue.value;
    },
    (current: number, previous: number | null) => {
      if (current !== previous) {
        // Re-rendering here causes the issue:
        runOnJS(forceReRender)();
      }
    },
  );

  function setRandomValue() {
    sharedValue.value = Math.random();
    // Re-rendering here works fine: (comment out above forceReRender)
    // forceReRender();
  }

  return (
    <>
      <Pressable style={styles.container} onPress={setRandomValue}>
        <Text>Random</Text>
      </Pressable>
      <Text>{sharedValue.value}</Text>
      <Text>{derivedValue.value}</Text>
    </>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 12,
    backgroundColor: 'yellow',
  },
});
thisisgit commented 2 years ago

Are there anyone who can look into this issue in the team? @jakub-gonet

Latropos commented 1 year ago

Generally Fast Refresh does not always work with Reanimated. Due to relatively small team and lot of more important bugs we can't afford fixing this, you can make everything work simply reloading your app.

Latropos commented 9 months ago

Closing, since we are unafortunatelly unable to fix it. Luckily the problem should disappear once you reload the app

scarlac commented 6 months ago

You can try to use the undocumented and "magic comment" that React Native looks for when determining how it should fast reload. The comment will disable state restoration on that specific component/file. So any useState()'s will reset when reloading, but it does fix this issue.

function MyAnimatedComponent() {
  // @refresh reset - Bug in reanimated2: Disable Fast Reload
  // https://github.com/software-mansion/react-native-reanimated/issues/1964
  const anim = useSharedValue(0); // tested for useSharedValue, not useDerivedValue.
  // ...
}