nandorojo / moti

🐼 The React Native (+ Web) animation library, powered by Reanimated 3.
https://moti.fyi
MIT License
3.91k stars 120 forks source link

Sequence not working since reanimated 2.3.0 #195

Closed msantang78 closed 1 year ago

msantang78 commented 2 years ago

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 commented 2 years ago

can you reproduce on a snack with SDK 44?

msantang78 commented 2 years ago

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 commented 2 years ago

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.

https://github.com/nandorojo/moti/blob/881be888512d6cc6b8746a706601c74a2a55ecbf/packages/core/src/use-map-animate-to-style.ts#L425

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.

https://github.com/nandorojo/moti/blob/881be888512d6cc6b8746a706601c74a2a55ecbf/packages/core/src/use-map-animate-to-style.ts#L345

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 commented 2 years ago

interesting find. can this be reproduced on snack?

nandorojo commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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

jstheoriginal commented 2 years ago

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 commented 2 years ago

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 commented 1 year ago

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

nandorojo commented 1 year ago

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

nandorojo commented 1 year ago

Sequences are now fixed in 0.19.0-alpha.2:

yarn add moti@canary

Please see #215 for the upgrade guide.

Thanks guys!

rrsalian16 commented 1 year ago

@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 commented 1 year ago

@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 commented 1 year ago

@jstheoriginal

Screenshot 2022-07-29 at 1 12 40 AM
nandorojo commented 1 year ago

no need to quote the big messages haha

could be a caching issue?

rrsalian16 commented 1 year ago

okay, thanks @nandorojo

but I cleared everything not resolved.

nandorojo commented 1 year ago

try yarn why framer-motion

rrsalian16 commented 1 year ago
Screenshot 2022-07-29 at 10 14 10 AM
nandorojo commented 1 year ago

🧐 what about yarn why moti

rrsalian16 commented 1 year ago
Screenshot 2022-07-29 at 4 16 12 PM
nandorojo commented 1 year ago

will try to fix it today, thanks!

rrsalian16 commented 1 year ago

Okay, thank you.

nandorojo commented 1 year ago

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

rrsalian16 commented 1 year ago

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