software-mansion / react-native-reanimated

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

SharedValue is an type of object, and sometimes a number #6000

Closed Michota closed 2 weeks ago

Michota commented 1 month ago

Description

I got following bug:

Invariant Violation: Transform with key of "translateX" must be a number: {"translateX":{"onFrame":{},"onStart":{},"toValue":168.21428571428572,"velocity":10.531789498949639,"current":164.5932737384055,"startValue":164.64457455258392,"lastTimestamp":13514092.617269,"startTimestamp":13513334,"zeta":0.5,"omega0":10,"omega1":8.660254037844386,"reduceMotion":false,"callStart":null,"timestamp":13514092.617269}}

This error is located at:
...

When I started to investigate what is the reason that it appears, i stumbled upon this weird behavior or SharedValue inside useAnimatedStyle: value is an object, but it should be a primitive (a number).

   const dislikeButtonStartPosition = useSharedValue({
      x: DISLIKE_BUTTON_START_POSITION.x,
      y: windowDimensions.height - 100,
    });
    const dislikeButtonOffset = useSharedValue({
      x: dislikeButtonStartPosition.value.x,
      y: dislikeButtonStartPosition.value.y,
    });
    const curtainOffset = useSharedValue(0);
    const curtainWidth = useSharedValue(0);

    const dislikeButtonSize = useSharedValue(DISLIKE_BUTTON_SIZE);

    console.log(dislikeButtonOffset.value.x, dislikeButtonOffset.value.y); // THERE THEY ARE LOGGED AS NUMBER

    const dislikeButtonAnimatedStyles = useAnimatedStyle(() => {
      console.log(dislikeButtonOffset.value.x, dislikeButtonOffset.value.y); // THERE THEY ARE LOGGED AS OBJECT

      return {
        backgroundColor: "red",
        position: "absolute",
        justifyContent: "center",
        alignItems: "center",
        borderRadius: 100,
        width: dislikeButtonSize.value,
        height: dislikeButtonSize.value,
        transform: [
          { translateX: dislikeButtonOffset.value.x },
          { translateY: dislikeButtonOffset.value.y },
          { scale: withSpring(isDislikeButtonHidden.value ? 0 : 1) },
        ],
      };
    });

When i log dislikeButtonOffset.value.x in console, i get

{
callStart: null
callback: undefined
current: 164.919688
lastTimestamp: 14029578.333438
omega0: 10
omega1: 8.660254
onFrame: {}
onStart: {}
reduceMotion: false
startTimestamp: 14028901
startValue: 93.168546
timestamp: 14029578.333438
toValue: 168.214286
velocity: -22.668363
zeta: 0.5
}

It wasn't like that a month ago, so i guess it has something to do with new version of react-native-reanimated.

Steps to reproduce

Described in previous section.

Snack or a link to a repository

I cant provide this, its a private project.

Reanimated version

3.10.1

React Native version

0.74.1

Platforms

Android

JavaScript runtime

Hermes

Workflow

Expo Dev Client

Architecture

Paper (Old Architecture)

Build type

Debug app & dev bundle

Device

Real device

Device model

Oneplus 9pro

Acknowledgements

Yes

github-actions[bot] commented 1 month ago

Hey! đŸ‘‹

The issue doesn't seem to contain a minimal reproduction.

Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?

szydlovsky commented 1 month ago

Hey @Michota would you be able to provide a minimal reproduction that contains the problem? Your example is quite complicated. Also, looking at the code I can tell at least one thing that is not completely fine: dislikeButtonOffset won't be a reactive shared value, because you create it depending on other shared value. I'd suggest using useDerivedValue in such situation.

Michota commented 1 month ago

Can you tell me why does this work? For some reason derivedValue as an object does not work, but it does work as two values.

    const dislikeButtonOffset = useSharedValue({
      x: dislikeButtonStartPosition.value.x,
      y: dislikeButtonStartPosition.value.y,
    });
    const curtainOffset = useSharedValue(0);
    const curtainWidth = useSharedValue(0);

    const dislikeButtonOffsetX = useDerivedValue(() => dislikeButtonOffset.value.x);
    const dislikeButtonOffsetY = useDerivedValue(() => dislikeButtonOffset.value.y);

    const dislikeButtonSize = useSharedValue(DISLIKE_BUTTON_SIZE);

    const dislikeButtonAnimatedStyles = useAnimatedStyle(() => {
      return {
        backgroundColor: "yellow",
        position: "absolute",
        justifyContent: "center",
        alignItems: "center",
        borderRadius: 100,
        width: dislikeButtonSize.value,
        height: dislikeButtonSize.value,
        transform: [
          { translateX: dislikeButtonOffsetX.value },
          { translateY: dislikeButtonOffsetY.value },    

But this does not?

 const dislikeButtonOffset = useSharedValue({
      x: dislikeButtonStartPosition.value.x,
      y: dislikeButtonStartPosition.value.y,
    });
    const curtainOffset = useSharedValue(0);
    const curtainWidth = useSharedValue(0);

    const dislikeButtonOffsetTwo = useDerivedValue(() => dislikeButtonOffset.value);

    const dislikeButtonSize = useSharedValue(DISLIKE_BUTTON_SIZE);

    const dislikeButtonAnimatedStyles = useAnimatedStyle(() => {
      return {
        backgroundColor: "yellow",
        position: "absolute",
        justifyContent: "center",
        alignItems: "center",
        borderRadius: 100,
        width: dislikeButtonSize.value,
        height: dislikeButtonSize.value,
        transform: [
          { translateX: dislikeButtonOffsetTwo.value.x },
          { translateY: dislikeButtonOffsetTwo.value.y },
Michota commented 1 month ago

Btw. changing dislikeButtonOffset to derived value will allow me to change its values in different functions, like this one? Currently I am animating this in following way:

    const curtainShrinkRight = useCallback(() => {
      runOnUI(() => {
        isDislikeButtonHidden.value = false;

        dislikeButtonOffset.value = {
          x: withTiming(dislikeButtonStartPosition.value.x, {
            duration: 0,
          }),
          y: withTiming(dislikeButtonStartPosition.value.y, {
            duration: 0,
          }),
        };
      })();

      curtainOffset.value = withTiming(0, {
        duration: DISLIKE_CURTAIN_TIME.shrink,
      });

      curtainWidth.value = withTiming(0, {
        duration: DISLIKE_CURTAIN_TIME.shrink,
      });
    }, [
      curtainOffset,
      curtainWidth,
      dislikeButtonOffset,
      isDislikeButtonHidden,
      dislikeButtonStartPosition.value.x,
      dislikeButtonStartPosition.value.y,
    ]);

// some skipped code...

    useEffect(() => {
      if (isExpanded === false) {
        curtainShrinkRight();
      }
    }, [isExpanded, curtainShrinkRight]);

How do i modify it? Using modify() does not work, maybe I dont understand something...

Michota commented 1 month ago

May this be related to fact, that animations are being run in UI thread?

    const curtainExpandRight = useCallback(() => {
      curtainWidth.value = withTiming(screenDimensions.width / 2, {
        duration: DISLIKE_CURTAIN_TIME.expand,
      });

      curtainOffset.value = withDelay(
        DISLIKE_CURTAIN_TIME.expand,
        withTiming(
          0,
          {
            duration: DISLIKE_CURTAIN_TIME.expand,
          },
          () => {
            runOnJS(onCurtainExpand)();
            isDislikeButtonHidden.value = true;
          }
        )
      );
    }, [curtainOffset, curtainWidth, screenDimensions.width, onCurtainExpand, isDislikeButtonHidden]);

    const curtainShrinkRight = useCallback(() => {
      runOnUI(() => {
        isDislikeButtonHidden.value = false;

        dislikeButtonOffset.value = {
          x: withTiming(dislikeButtonStartPosition.x, {
            duration: 0,
          }),
          y: withTiming(dislikeButtonStartPosition.y, {
            duration: 0,
          }),
        };
      })();
tjzel commented 1 month ago

Hi @Michota. I ran your first code snippet in the following way:

import React from 'react';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';

const DISLIKE_BUTTON_START_POSITION = {x: 0, y: 0};

const windowDimensions = {height: 100};

const DISLIKE_BUTTON_SIZE = 100;

export default function App() {
  const isDislikeButtonHidden = useSharedValue(false);
  const dislikeButtonStartPosition = useSharedValue({
    x: DISLIKE_BUTTON_START_POSITION.x,
    y: windowDimensions.height - 100,
  });
  const dislikeButtonOffset = useSharedValue({
    x: dislikeButtonStartPosition.value.x,
    y: dislikeButtonStartPosition.value.y,
  });

  const dislikeButtonSize = useSharedValue(DISLIKE_BUTTON_SIZE);

  console.log(dislikeButtonOffset.value.x, dislikeButtonOffset.value.y); // THERE THEY ARE LOGGED AS NUMBER

  const dislikeButtonAnimatedStyles = useAnimatedStyle(() => {
    console.log(dislikeButtonOffset.value.x, dislikeButtonOffset.value.y); // THERE THEY ARE LOGGED AS OBJECT

    return {
      backgroundColor: 'red',
      position: 'absolute',
      justifyContent: 'center',
      alignItems: 'center',
      borderRadius: 100,
      width: dislikeButtonSize.value,
      height: dislikeButtonSize.value,
      transform: [
        {translateX: dislikeButtonOffset.value.x},
        {translateY: dislikeButtonOffset.value.y},
        {scale: withSpring(isDislikeButtonHidden.value ? 0 : 1)},
      ],
    };
  });

  return <Animated.View style={dislikeButtonAnimatedStyles} />;
}

And everything works fine. From what you posted it seems like you have misassigned an animation somewhere to a shared value. If you want us to help you, I'm afraid you'd need to submit a complete component code (or a reproducible example) instead of little bits of it.