software-mansion / react-native-reanimated

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

Crash when using a class inside useDerivedValue hook #1847

Closed iagormoraes closed 2 months ago

iagormoraes commented 3 years ago

Description

There is a way to make a instance of a class inside the useDerivedValue hook? I'm trying to make it, is a simple class with only JS code but the app crashes when I try. I have added worklet string to every possible place but no effect.

Actual behavior

App crashes when trying to make a class instance inside hook.

Snack or minimal code example

    useDerivedValue(() => {
        const simpleClass = new MySimpleClass(100);

        return ''
    })

Package versions

github-actions[bot] commented 3 years ago

Issue validator

The issue is invalid!

mrousavy commented 3 years ago

While I realize that this is an issue that should be fixed, you're probably doing something really weird. Why would you want to create a new class in a reanimated UI worklet?

iagormoraes commented 3 years ago

While I realize that this is an issue that should be fixed, you're probably doing something really weird. Why would you want to create a new class in a derived value hook?

I wanted to know if we should be strict only to functional coding style due to reanimated2 to create a context of that called function to the UI thread, I raised that issue because sometimes we use a 3rd party package that doesn't have a worklet on top of the function or is created using classes like Intl.

wanderSX commented 3 years ago

Having similar issue. Can't call this.someMethod() inside class, because of this

jakub-gonet commented 2 years ago

A future reference for bug fixing is below.

Using playground app as an example:

import Animated, {
  useSharedValue,
  withTiming,
  useAnimatedStyle,
  useDerivedValue,
  Easing,
} from 'react-native-reanimated';
import {View} from 'react-native';
import React from 'react';

class MySimpleClass {
  constructor() {
    this.someProp = '50%';
  }

  testFn() {
    'worklet';
    return this.someProp;
  }
}

export default function AnimatedStyleUpdateExample(props) {
  const randomWidth = useSharedValue(10);

  const config = {
    duration: 500,
    easing: Easing.bezier(0.5, 0.01, 0, 1),
  };
  const simpleClass = new MySimpleClass();

  const derivedFromClassFn = useDerivedValue(() => simpleClass.testFn());

  const style = useAnimatedStyle(() => {
    return {
      width: withTiming(derivedFromClassFn.value, config),
    };
  });

  return (
    <View
      style={{
        flex: 1,
        flexDirection: 'column',
      }}>
      <Animated.View
        style={[
          {width: 100, height: 80, backgroundColor: 'black', margin: 30},
          style,
        ]}
      />
    </View>
  );
}

It's compiled into:

  var MySimpleClass = function () {
    function MySimpleClass() {
      (0, _classCallCheck2.default)(this, MySimpleClass);
      this.someProp = '50%';
    }

    (0, _createClass2.default)(MySimpleClass, [{
      key: "testFn",
      value: function () {
        var _f = function _f() {
          return this.someProp;
        };

        _f._closure = {};
        _f.asString = "function testFn(){return this.someProp;}";
        _f.__workletHash = 9299864541580;
        _f.__location = "/Users/jgonet/Projects/reanimated-2-playground/Screen.js";

        global.__reanimatedWorkletInit(_f);

        return _f;
      }()
    }]);
    return MySimpleClass;
  }();

  function AnimatedStyleUpdateExample(props) {
    _s();

    var randomWidth = (0, _reactNativeReanimated.useSharedValue)(10);
    var config = {
      duration: 500,
      easing: _reactNativeReanimated.Easing.bezier(0.5, 0.01, 0, 1)
    };
    var simpleClass = new MySimpleClass();
    var derivedFromClassFn = (0, _reactNativeReanimated.useDerivedValue)(function () {
      var _f = function _f() {
        return simpleClass.testFn();
      };

      _f._closure = {
        simpleClass: {
          testFn: simpleClass.testFn
        }
      };
      _f.asString = "function _f(){const{simpleClass}=jsThis._closure;{return simpleClass.testFn();}}";
      _f.__workletHash = 1712627337575;
      _f.__location = "/Users/jgonet/Projects/reanimated-2-playground/Screen.js (31:45)";

      global.__reanimatedWorkletInit(_f);

      return _f;
    }());
    var style = (0, _reactNativeReanimated.useAnimatedStyle)(function () {
      var _f = function _f() {
        return {
          width: (0, _reactNativeReanimated.withTiming)(derivedFromClassFn.value, config)
        };
      };

      _f._closure = {
        withTiming: _reactNativeReanimated.withTiming,
        derivedFromClassFn: derivedFromClassFn,
        config: config
      };
      _f.asString = "function _f(){const{withTiming,derivedFromClassFn,config}=jsThis._closure;{return{width:withTiming(derivedFromClassFn.value,config)};}}";
      _f.__workletHash = 15289630804484;
      _f.__location = "/Users/jgonet/Projects/reanimated-2-playground/Screen.js (33:33)";
      _f.__optimalization = 2;

      global.__reanimatedWorkletInit(_f);

      return _f;
    }());
// (...)

We don't pass this correctly as you said.

Youssef-Durgham commented 1 year ago

fixed by doing that expo start -c
will fix your error

szydlovsky commented 2 months ago

Hey everyone, an udpdate from the future I guess 😄 Hermes doesn't support sending class objects to UI Runtime, and the current reanimated crash states it explicitly:

image

So as for now, we don't see it as an issue, more like a limitation.