moti: Sequence not working since reanimated 2.3.0

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

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 28 (19 by maintainers)

Commits related to this issue

Most upvoted comments

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

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)

Thank you @nandorojo It is resolved. 👏👏

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

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}