nandorojo / moti

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

useDynamicAnimation initial state when screen re-mount #249

Closed filippobarcellos closed 1 year ago

filippobarcellos commented 1 year ago

Is there an existing issue for this?

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

https://user-images.githubusercontent.com/56128203/209007243-3ed0b110-d35b-43a2-b65f-0eb595831f1b.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.

nandorojo commented 1 year ago

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?

nandorojo commented 1 year ago

Also, can you try upgrading Moti?

nandorojo commented 1 year ago

Is this just a fast refresh issue?

filippobarcellos commented 1 year ago

Sorry. Realized I was not clear.

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

My animation consists of two things:

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)

https://user-images.githubusercontent.com/56128203/209016029-f794f043-a5cd-4d94-a38a-e1f30b0174d9.mov

nandorojo commented 1 year ago

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.

nandorojo commented 1 year ago

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

filippobarcellos commented 1 year ago

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:

https://user-images.githubusercontent.com/56128203/209017691-be97578a-436a-4a73-bf46-beef1ef9a447.mov

Actual behavior:

https://user-images.githubusercontent.com/56128203/209017764-2b53863f-c23c-4453-aac0-42b21d643ac6.mov

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

nandorojo commented 1 year ago

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.

filippobarcellos commented 1 year ago

Could you provide an example, please?

Thanks for your help!

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

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

  if (forceUpdate) { 
    //...
  }

  return greenCircle
}
filippobarcellos commented 1 year ago

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.

nandorojo commented 1 year ago

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.