Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sequence not working since reanimated 2.3.0 #195

Closed
msantang78 opened this issue May 8, 2022 · 28 comments · Fixed by #200
Closed

Sequence not working since reanimated 2.3.0 #195

msantang78 opened this issue May 8, 2022 · 28 comments · Fixed by #200

Comments

@msantang78
Copy link

Since reanimated 2.3.0 I was no longer able to use Moti sequence animations

I'm getting the error:

Exception in HostFunction: Javascript worklet error

For any sequence animation

  <MotiView from={{scale: 1}} animate={{scale: [1, 1.5, 1, 1.5, 1]}}>
    <Text>Test</Test>
  </MotiView>

I tested updating reanimated to different versions (even the last one) without luck

While creating a minimal app to reproduce the issue I found that the problem seems to be with the Hermes Engine. If Hermes is turned off the sequences seem to work properly

@nandorojo I created a repo using react-native init to reproduce the issue on iOS (adding only reanimated, moti, and enabling Hermes)

https://github.com/msantang78/MotiSequenceReproduction

I also added an animation with reanimated sequences and they seem to be working fine.

I hope it helps

For reference:
I originally reported this on #131, there were other reports #140 #183

@nandorojo
Copy link
Owner

can you reproduce on a snack with SDK 44?

@msantang78
Copy link
Author

But the problem seems to be with the Hermes engine, I'm not sure if there is a way of enabling Hermes on a snack

@jstheoriginal
Copy link
Contributor

Thanks for reporting this issue. I'm also seeing this when trying to upgrade.

The issue seems to happen when it tries to call this line, since stepAnimation is undefined.

const sequenceValue = stepAnimation(stepValue, stepConfig, callback)

It references this destructured animation outside of the getSequenceArray const, which does have a value when it's destructured, but is then undefined when it's used in getSequenceArray.

example of logs:

 LOG  === animation value right after destructured from animationConfig() {"animation": [Function hostFunction]}
 LOG  === getSequenceArray called for {"sequenceArray": [0.25, 1.25, 1.25, 1.25, 0.25, 0.25, 0.25, 1.25, 0.25, 0.25], "sequenceKey": "opacity"}
 LOG  === value of stepAnimation (animation) just before stepAnimation called {"stepAnimation": undefined}

@nandorojo
Copy link
Owner

interesting find. can this be reproduced on snack?

@nandorojo
Copy link
Owner

if the issue is hermes, then i assume it’s a reanimated issue? or is it the fact that i’m using a function with moti to build the sequence?

@jstheoriginal
Copy link
Contributor

If i disable hermes, it gets further (the result of the function at least finishes), but it's freezing for me still, so it might not be an hermes issue per se (it might just break in a different way). I'll see if a snack can use hermes.

 LOG  === animation value right after destructured from animationConfig() {"animation": [Function hostFunction]}
 LOG  === getSequenceArray called for {"sequenceArray": [1.25, 1.25, 0.25, 0.25, 0.25, 1.25, 1.25, 1.25, 1.25, 0.25], "sequenceKey": "scale"}
 LOG  === value of stepAnimation (animation) just before stepAnimation called {"stepAnimation": [Function hostFunction]}
 LOG  === result of getSequenceArray: {"sequence": [{"callback": [Function hostFunction], "current": 1.25, "easing": [Function hostFunction], "onFrame": [Function hostFunction], "onStart": [Function hostFunction], "progress": 0, "startTime": 0, "startValue": 0, "toValue": 1.25, "type": "timing"}, {"callback": [Function hostFunction], "current": 1.25, "easing": [Function hostFunction], "onFrame": [Function hostFunction], "onStart": [Function hostFunction], "progress": 0, "startTime": 0, "startValue": 0, "toValue": 1.25, "type": "timing"}, {"callback": [Function hostFunction], "current": 0.25, "easing": [Function hostFunction], "onFrame": [Function hostFunction], "onStart": [Function hostFunction], "progress": 0, "startTime": 0, "startValue": 0, "toValue": 0.25, "type": "timing"}, {"callback": [Function hostFunction], "current": 0.25, "easing": [Function hostFunction], "onFrame": [Function hostFunction], "onStart": [Function hostFunction], "progress": 0, "startTime": 0, "startValue": 0, "toValue": 0.25, "type": "timing"}, {"callback": [Function hostFunction], "current": 0.25, "easing": [Function hostFunction], "onFrame": [Function hostFunction], "onStart": [Function hostFunction], "progress": 0, "startTime": 0, "startValue": 0, "toValue": 0.25, "type": "timing"}, {"callback": [Function hostFunction], "current": 1.25, "easing": [Function hostFunction], "onFrame": [Function hostFunction], "onStart": [Function hostFunction], "progress": 0, "startTime": 0, "startValue": 0, "toValue": 1.25, "type": "timing"}, {"callback": [Function hostFunction], "current": 1.25, "easing": [Function hostFunction], "onFrame": [Function hostFunction], "onStart": [Function hostFunction], "progress": 0, "startTime": 0, "startValue": 0, "toValue": 1.25, "type": "timing"}, {"callback": [Function hostFunction], "current": 1.25, "easing": [Function hostFunction], "onFrame": [Function hostFunction], "onStart": [Function hostFunction], "progress": 0, "startTime": 0, "startValue": 0, "toValue": 1.25, "type": "timing"}, {"callback": [Function hostFunction], "current": 1.25, "easing": [Function hostFunction], "onFrame": [Function hostFunction], "onStart": [Function hostFunction], "progress": 0, "startTime": 0, "startValue": 0, "toValue": 1.25, "type": "timing"}, {"callback": [Function hostFunction], "current": 0.25, "easing": [Function hostFunction], "onFrame": [Function hostFunction], "onStart": [Function hostFunction], "progress": 0, "startTime": 0, "startValue": 0, "toValue": 0.25, "type": "timing"}]}

@jstheoriginal
Copy link
Contributor

jstheoriginal commented May 17, 2022

So even without hermes, the sequence with Moti doesn't work despite it working with Reanimated, so I'm thinking there's an issue with the function.

I took @msantang78's repo and set moti to 0.18.0 and reanimted to 2.3.1 (the highest expo 0.44 allows currently).

When you run it, you can see that only the Reanimated sequence works on iOS and Android (web works):

https://snack.expo.dev/lTiRIGySt

However, I also tried to get it to work on reanimated 2.2.0 (what expo 0.43 allows), and it doesn't work either for moti (i even made sure to use the transform array since it's less than reanimated 2.3):

https://snack.expo.dev/NZSL2U0FE

So I don't think snacks work, unfortunately, due to the limitations of expo with reanimated.

@jstheoriginal
Copy link
Contributor

or is it the fact that i’m using a function with moti to build the sequence?

So I just removed the function and just do what the function does inline and the issue doesn't happen anymore. 🎉 So it looks like it's because of the inline getSequenceArray const like you suspected.

@jstheoriginal
Copy link
Contributor

jstheoriginal commented May 17, 2022

this patch-package diff resolves the issue (extracted function and marked it as a worklet).

diff --git a/node_modules/@motify/core/src/use-map-animate-to-style.ts b/node_modules/@motify/core/src/use-map-animate-to-style.ts
index dce4aa1..9cda484 100644
--- a/node_modules/@motify/core/src/use-map-animate-to-style.ts
+++ b/node_modules/@motify/core/src/use-map-animate-to-style.ts
@@ -220,6 +220,64 @@ function animationConfig<Animate>(
   }
 }
 
+// this used to be an inline function, but it caused the error outlined here:
+// https://github.com/nandorojo/moti/issues/195
+const getSequenceArray = (
+  sequenceKey: string,
+  sequenceArray: SequenceItem<any>[],
+  delayMs: number,
+  config: {},
+  animation: (...props: any) => any,
+  callback: (completed: boolean, value?: any) => void
+) => {     
+  'worklet'
+  
+  const sequence: any[] = []
+
+  for (const step of sequenceArray) {
+    const shouldPush =
+      typeof step === 'object'
+        ? step && step?.value != null && step?.value !== false
+        : step != null && step !== false
+    if (shouldPush) {
+      let stepDelay = delayMs
+      let stepValue = step
+      let stepConfig = Object.assign({}, config)
+      let stepAnimation = animation
+
+      if (typeof step === 'object') {
+        // not allowed in Reanimated: { delay, value, ...transition } = step
+        const stepTransition = Object.assign({}, step)
+
+        delete stepTransition.delay
+        delete stepTransition.value
+
+        const { config: inlineStepConfig, animation } = animationConfig(
+          sequenceKey,
+          stepTransition
+        )
+
+        stepConfig = Object.assign({}, stepConfig, inlineStepConfig)
+        stepAnimation = animation
+
+        if (step.delay != null) {
+          stepDelay = step.delay
+        }
+        stepValue = step.value
+      }
+
+      const sequenceValue = stepAnimation(stepValue, stepConfig, callback)
+      if (stepDelay != null) {
+        sequence.push(withDelay(stepDelay, sequenceValue))
+      } else {
+        sequence.push(sequenceValue)
+      }
+    }
+  }
+
+  return sequence
+}
+
 export function useMotify<Animate>({
   animate: animateProp,
   from: fromProp = false,
@@ -385,55 +443,6 @@ export function useMotify<Animate>({
         continue
       }
 
-      const getSequenceArray = (
-        sequenceKey: string,
-        sequenceArray: SequenceItem<any>[]
-      ) => {
-        const sequence: any[] = []
-
-        for (const step of sequenceArray) {
-          const shouldPush =
-            typeof step === 'object'
-              ? step && step?.value != null && step?.value !== false
-              : step != null && step !== false
-          if (shouldPush) {
-            let stepDelay = delayMs
-            let stepValue = step
-            let stepConfig = Object.assign({}, config)
-            let stepAnimation = animation
-            if (typeof step === 'object') {
-              // not allowed in Reanimated: { delay, value, ...transition } = step
-              const stepTransition = Object.assign({}, step)
-
-              delete stepTransition.delay
-              delete stepTransition.value
-
-              const { config: inlineStepConfig, animation } = animationConfig(
-                sequenceKey,
-                stepTransition
-              )
-
-              stepConfig = Object.assign({}, stepConfig, inlineStepConfig)
-              stepAnimation = animation
-
-              if (step.delay != null) {
-                stepDelay = step.delay
-              }
-              stepValue = step.value
-            }
-
-            const sequenceValue = stepAnimation(stepValue, stepConfig, callback)
-            if (stepDelay != null) {
-              sequence.push(withDelay(stepDelay, sequenceValue))
-            } else {
-              sequence.push(sequenceValue)
-            }
-          }
-        }
-
-        return sequence
-      }
-
       if (key === 'transform') {
         if (!Array.isArray(value)) {
           console.error(
@@ -448,7 +457,7 @@ export function useMotify<Animate>({
 
             if (Array.isArray(transformValue)) {
               // we have a sequence in this transform...
-              const sequence = getSequenceArray(transformKey, transformValue)
+              const sequence = getSequenceArray(transformKey, transformValue, delayMs, config, animation, callback)
 
               if (sequence.length) {
                 let finalValue = withSequence(sequence[0], ...sequence.slice(1))
@@ -484,8 +493,7 @@ export function useMotify<Animate>({
         }
       } else if (Array.isArray(value)) {
         // we have a sequence
-        const sequence = getSequenceArray(key, value)
+        const sequence = getSequenceArray(key, value, delayMs, config, animation, callback)
         let finalValue = withSequence(sequence[0], ...sequence.slice(1))
         if (shouldRepeat) {
           finalValue = withRepeat(finalValue, repeatCount, repeatReverse)

@nandorojo
Copy link
Owner

if that patch fixed it, why did you close your PR?

@jstheoriginal
Copy link
Contributor

if that patch fixed it, why did you close your PR?

😐 I didn't close it...but it says I did. 😲

I'll reopen it.

@jstheoriginal
Copy link
Contributor

if that patch fixed it, why did you close your PR?

😐 I didn't close it...but it says I did. 😲

I'll reopen it.

It looks like when i referenced the PR on our private repo and merged changes there, it closed the issue. Just reopened it! It definitely resolves the issue still. 💯

@johkade
Copy link

johkade commented Jul 21, 2022

just fyi: this is still the case with moti 0.18, also the patch above still works 👍

@nandorojo
Copy link
Owner

just merged @jstheoriginal's PR, thanks for your patience here. will release it in the next version

@nandorojo
Copy link
Owner

Sequences are now fixed in 0.19.0-alpha.2:

yarn add moti@canary

Please see #215 for the upgrade guide.

Thanks guys!

@rrsalian16
Copy link

@nandorojo I loved moti,

I tried 0.19.0-alpha.2 & getting this error

error: Error: Unable to resolve module framer-motion from /node_modules/moti/src/core/index.ts: framer-motion could not be found within the project or in these directories:
node_modules
../../../node_modules
1 | export { default as motify } from './motify'

2 | export { AnimatePresence } from 'framer-motion'
| ^
3 |
4 | export * from './types'
5 | export { default as useAnimationState } from './use-animator'

Screenshot 2022-07-29 at 12 46 01 AM

@jstheoriginal
Copy link
Contributor

@nandorojo I loved moti,

I tried 0.19.0-alpha.2 & getting this error

error: Error: Unable to resolve module framer-motion from /node_modules/moti/src/core/index.ts: framer-motion could not be found within the project or in these directories:

node_modules

../../../node_modules

1 | export { default as motify } from './motify'

2 | export { AnimatePresence } from 'framer-motion'

|                                  ^

3 |

4 | export * from './types'

5 | export { default as useAnimationState } from './use-animator'

Screenshot 2022-07-29 at 12 46 01 AM

Import AnimatePresence from moti instead of framer-motion.

@rrsalian16
Copy link

rrsalian16 commented Jul 28, 2022

@jstheoriginal
Screenshot 2022-07-29 at 1 12 40 AM

@nandorojo
Copy link
Owner

no need to quote the big messages haha

could be a caching issue?

@rrsalian16
Copy link

rrsalian16 commented Jul 28, 2022

okay, thanks @nandorojo

but I cleared everything not resolved.

@nandorojo
Copy link
Owner

try yarn why framer-motion

@rrsalian16
Copy link

Screenshot 2022-07-29 at 10 14 10 AM

@nandorojo
Copy link
Owner

🧐 what about yarn why moti

@rrsalian16
Copy link

Screenshot 2022-07-29 at 4 16 12 PM

@nandorojo
Copy link
Owner

will try to fix it today, thanks!

@rrsalian16
Copy link

Okay, thank you.

@nandorojo
Copy link
Owner

Can you try updating again? yarn add moti@canary. 0.19.0-alpha.6 should work now.

@rrsalian16
Copy link

Thank you @nandorojo It is resolved. 👏👏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants