software-mansion / react-native-reanimated

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

Does useAnimatedStyle have access to component props? #1014

Closed MinimogDev closed 3 years ago

MinimogDev commented 4 years ago

Description

I am passing a prop to my component named reversed. It's a boolean and I want to adjust animation logic based on this prop. Whenever I log it inside useAnimatedStyle callback it is always undefined even though it is defined in component itself. I tried to pass it to useSharedValue but still had not luck.

I am not sure if this is a bug or me miss-using / miss-understanding these hooks, so decided to open this issue as a question.

With code below I get console output like following:

outside true
inside undefined

Code

function MyComponent({ reversed }) {
   console.log('outside', reversed);
   const animation = useSharedValue(0);
   const animation = useAnimatedStyle(() => {
       return {
           height: withTiming(animated.value, null, () => console.log('inside', reversed));
       };
   });

   /* rest */
}

Package versions

terrysahaidak commented 4 years ago

Could you try to define callback outside of useAnimatedStyle? I thing this should work.

But still it seems like a bug. There is some babel magic behind capturing all the variables used inside useAnimatedStyle hook. Maybe it messed around with this reversed variable.

MinimogDev commented 4 years ago

@terrysahaidak Just tried with callback being outside, same result. Changing prop name didn't help either

Updated attempt:

  function callback() {
    console.log('inside', rewind);
  }

  const animation = useAnimatedStyle(() => {
    return {
      transform: [
        {
          translateY: withTiming(animated.value, null, callback)
        }
      ]
    };
  });

If it helps, here is full component code

import React, { useEffect, useState } from 'react';
import { Dimensions } from 'react-native';
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
import { SafeAreaView } from 'react-native-safe-area-context';
import s from './style';

/**
 * Utilities
 */
const { height } = Dimensions.get('window');

/**
 * Types
 */
interface Props {
  children: React.ReactNode | React.ReactNode[];
  index: number | undefined;
  rewind?: boolean;
}

/**
 * Component
 */
function RouteTransition({ children, index, rewind }: Props) {
  const [visible, setVisible] = useState(true);
  const animated = useSharedValue(rewind ? 0 : height);
  console.log('outside', rewind);

  function callback() {
    console.log('inside', rewind);
  }

  const animation = useAnimatedStyle(() => {
    return {
      transform: [
        {
          translateY: withTiming(animated.value, null, callback)
        }
      ]
    };
  });

  useEffect(() => {
    animated.value = rewind ? height : 0;
  }, [animated, rewind]);

  return visible ? (
    <SafeAreaView style={[s.safeArea, { zIndex: 5 + Number(index) }]}>
      <Animated.View style={[s.animatedView, animation]}>{children}</Animated.View>
    </SafeAreaView>
  ) : null;
}

export default RouteTransition;
terrysahaidak commented 4 years ago

I couldn't reproduce this issue. What exact version of Reanimated2 did you use? Make sure you used Reanimated's babel plugin.

MinimogDev commented 4 years ago

@terrysahaidak latest alpha. I will try to replicate it outside of my project today and make a snack. And yep, I'm using the plugin

jakub-gonet commented 4 years ago

Snack won't help here, as the Reanimated alpha isn't included in the expo at the moment. Just paste the entire code here in the code block.

MinimogDev commented 4 years ago

@jakub-gonet it's pretty much all here https://github.com/software-mansion/react-native-reanimated/issues/1014#issuecomment-663427421 I'm in clean react native 0.63.2 project with only addition being reanimated's babel plugin. Also using typescript.

terrysahaidak commented 4 years ago

Or would be cool to have a repository reproducing this so we can see the same result you have with the same env.

MinimogDev commented 4 years ago

@terrysahaidak did you try to reproduce it on master or latest alpha? Repo I am seeing this in is private so I can't share it, I need to find some time and set up small reproduction repo, prob this weekend. But I am reliably able to reproduce this in different scenarios using different variable names / values.

Not sure if related but I also noticed that when I wrap animation functions like withTiming inside delay then animation callback is not triggered at all. (Someone opened related issue #1057)

terrysahaidak commented 4 years ago

I tested this on latest alpha as well as on master branch. Also, I didn't see this behavior previously.

jakub-gonet commented 4 years ago

@MinimogDev, could you at least share your babel config? This is likely connected with the plugin setup.

MinimogDev commented 4 years ago

@jakub-gonet sure, here it is

module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: [
    [
      'module-resolver',
      {
        root: ['.'],
        extensions: ['.ts', '.tsx', '.json'],
        alias: {
          $app: './src',
          $images: './assets/images'
        }
      }
    ],
    'react-native-reanimated/plugin'
  ]
};

Related package.json entries

"@babel/core": "^7.10.5",
"@babel/runtime": "^7.10.5",
"react-native-reanimated": "alpha",
"react-native": "0.63.2",
"babel-plugin-module-resolver": "^4.0.0",
"metro-react-native-babel-preset": "^0.59.0"

I guess module resolver plugin could have something to do here? Other than that this is a pretty standard non-monorepo react native setup

karol-bisztyga commented 3 years ago

This apparently has been fixed by the way in on of the alpha versions that have been released since this was reported. This code works just fine:

import Animated, {
  withTiming,
  useAnimatedStyle,
} from 'react-native-reanimated';
import { View, Button } from 'react-native';
import React, { useState } from 'react';

function MyComponent({ reversed }) {
   console.log('outside', reversed);
   const styles = useAnimatedStyle(() => {
     const h = (reversed) ? 200 : 300;
       return {
          height: withTiming(h, null, () => console.log('inside', reversed)),
       };
   });

   return (
     <View>
       <Animated.View style={[{width: 100, height: 100, backgroundColor: 'orange'}, styles]} />
     </View>
   )
}

export default function Parent() {
  const [state, setState] = useState(false);

  console.log('render parent ' + state)

  return (
    <View>
      <Button title="change state" onPress={()=>{
        setState(!state)
      }} />
      <MyComponent reversed={ state } />
    </View>
  );
}
karol-bisztyga commented 3 years ago

@MinimogDev can this issue be closed?

terrysahaidak commented 3 years ago

@karol-bisztyga I would suggest to at least pass reversed directly to dependencies, so it's more clear why it's working. By default if I remember correctly it will recreate animated styles on each render that may create additional problems.

karol-bisztyga commented 3 years ago

@karol-bisztyga I would suggest to at least pass reversed directly to dependencies, so it's more clear why it's working. By default if I remember correctly it will recreate animated styles on each render that may create additional problems.

What difference would it make if reversed was passed to dependencies or not(besides clarity)? What problems may come up when recreating animated styles on every render?

terrysahaidak commented 3 years ago

What difference would it make if reversed was passed to dependencies or not(besides clarity)? What problems may come up when recreating animated styles on every render?

At least for future readers. There is not mention about dependencies and why it works in documentation at the moment. But also it might be a problem for something more "real life" :)

I had to pass ty arrays for each of my hook to avoid some of the bugs after upgrading. But I'm agree my usecase are way far from most of the users. But still less magic is better :)