nandorojo/moti

Sequence not working since reanimated 2.3.0

msantang78 opened this issue Β· 28 comments

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

can you reproduce on a snack with SDK 44?

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

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}

interesting find. can this be reproduced on snack?

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?

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"}]}

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.

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.

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)

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

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.

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. πŸ’―

just fyi: this is still the case with moti 0.18, also the patch above still works πŸ‘

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

Sequences are now fixed in 0.19.0-alpha.2:

yarn add moti@canary

Please see #215 for the upgrade guide.

Thanks guys!

@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

@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.

no need to quote the big messages haha

could be a caching issue?

okay, thanks @nandorojo

but I cleared everything not resolved.

try yarn why framer-motion

🧐 what about yarn why moti

will try to fix it today, thanks!

Okay, thank you.

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

Thank you @nandorojo It is resolved. πŸ‘πŸ‘