nandorojo/moti

useDynamicAnimation initial state when screen re-mount

filippobarcellos opened this issue · 12 comments

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

useDynamicAnimation state returns the last state when the screen re-mount.

Expected Behavior

I expect the useDynamicAnimation state returns the initial state instead of the last state.

Steps To Reproduce

const redCirclAnimation = useDynamicAnimation(() => {
    return {
      scale: 0,
      opacity: 0,
    };
  });

  const greenCirclAnimation = useDynamicAnimation(() => {
    return {
      scale: 1,
      opacity: 1,
    };
  });

  function onAnimate() {
    redCirclAnimation.animateTo({
      scale: [1, { value: 0, delay: 2000 }],
      opacity: [1, { value: 0, delay: 2000 }],
    });

    greenCirclAnimation.animateTo({
      scale: [0, { value: 1, delay: 2000 }],
      opacity: [0, { value: 1, delay: 2000 }],
    });
  }

Versions

- Moti: 0.11.0
- Reanimated: 2.9.1
- React Native: 0.69.4

Screenshots

Screen.Recording.2022-12-21.at.21.38.17.mov

Reproduction

1 - green circle should appear on the first render
2 - animate and show the red circle for a few seconds then show the green again
3 - screen gets refreshed for another reason
4 - red circle appears first. (clearly, the animation is running here although I don't want this to happen)

How do I reset the animation after is done? I feel like I'm missing something here.

Appreciate the help.

Hey, I think I’m not exactly understanding what it is that you want to happen vs. what is happening. What’s the simplest reproduction? Also, does wrapping your moti view in useMemo help?

Also, can you try upgrading Moti?

Is this just a fast refresh issue?

Sorry. Realized I was not clear.

The gist here -> https://gist.github.com/filippobarcellos/9197161e865f65442614212d7de2bbff

My animation consists of two things:

  • First: Hide the green circle
  • Second: Show the red circle
  • Third: Reset the animation by hiding the red circle and showing the green again. (done via useDynamicAnimation)
redCirclAnimation.animateTo({
      scale: [1, { value: 0, delay: 2000 }],
      opacity: [1, { value: 0, delay: 2000 }],
    });

    greenCirclAnimation.animateTo({
      scale: [0, { value: 1, delay: 2000 }],
      opacity: [0, { value: 1, delay: 2000 }],
    });

The green circle has the initial value as this:

const greenCirclAnimation = useDynamicAnimation(() => {
    return {
      scale: 1,
      opacity: 1,
    };
  });

Somehow after an animation occurs for the first time when I refresh the screen the same animation is happening again. (see new video)

Screen.Recording.2022-12-21.at.22.37.31.mov

Triggering re-renders shouldn't reset the animation state. That would be a bad behavior. In place of forceUpdate() just set the animation state to what you want it to be.

To be honest I still don't really get what the expected vs actual behavior is.

That's the thing. forceUpdate() has nothing to do with the animation and somehow it seems to be affecting it.

Isn't this part resetting it?

greenCirclAnimation.animateTo({
      scale: [0, { value: 1, delay: 2000 }],
      opacity: [0, { value: 1, delay: 2000 }],
    });

In the sequence, both scale and opacity get back to 1 which is the initial value. So why when re-render the red circle is automatically animating?

Expected behavior:

Screen.Recording.2022-12-21.at.22.49.59.mov

Actual behavior:

Screen.Recording.2022-12-21.at.22.50.21.mov

When I refresh the screen I don't wanna see the red circle and somehow it gets animated.

Hm, well one reason is that the components themselves are fully unmounting and then remounting. So when it remounts, it does the sequence animation. One way around it might be to hoist the circle as a variable at the top of the component inside of useMemo.

Could you provide an example, please?

Thanks for your help!

function App() {
  const greenCirclAnimation = useDynamicAnimation(() => {
    return {
      scale: 1,
      opacity: 1,
    };
  });

  const greenCircle = useMemo(() => 
    <MotiView state={greenCirclAnimation} style={styles.goal} />, 
  [])

  if (forceUpdate) { 
    //...
  }

  return greenCircle
}

Hm, well one reason is that the components themselves are fully unmounting and then remounting. So when it remounts, it does the sequence animation. One way around it might be to hoist the circle as a variable at the top of the component inside of useMemo.

I got that but in this case why does the animation is running? The trigger is the button. Not sure if I fully understand the use of useDynamicAnimation though.

It's because reanimated takes a style prop, and on the first render of that component, checks if the animation has run yet or not. Because it hasn't, it re-runs since you're using a sequence. If you weren't using a sequence, this wouldn't happen. I'm going to close this since I assume that my solution would address it, unless it doesn't.