Monadical-SAS / redux-time

∞ High-performance declarative JS animation library for building games, data-viz experiences, and more w/ React, ThreeJS, Inferno, SnabbDOM and others...
https://monadical-sas.github.io/redux-time
MIT License
119 stars 11 forks source link

Dispatch an action on animation end? #32

Open staff0rd opened 3 years ago

staff0rd commented 3 years ago

Looking to dispatch an arbitrary action (once) on animation/sequence end. I tried hax Animate at the end of a Sequential with something like:

Animate({
  path: "/whatever",
  state: 1,
  duration: 1,
  tick: () => {
    dispatch(someAction());
    return 1;
  },
}),

But this throws

Error: You may not call store.getState() while the reducer is executing. The reducer has already received the state as an argument. Pass it down from the top reducer instead of reading it from the store.

Also used Become to set some property to values like Not Animating, Animating and Complete and used middleware to intercept TICK, check for Complete and dispatch Not Animating, but the property is Complete at multiple intercepts before the dispatch finishes.

Any pointers on how I might dispatch a single action at animation end?

pirate commented 3 years ago

An important foundational principle of this library is that you are not allowed to dispatch actions from the animation system, the state flows unidirectionally from your UI controller -> actions -> animations. We found that allowing animations to re-dispatch actions later on only causes pain, as it leads to inscrutable circular dependencies / side effects that are hard to track down.

This is because the library is based on the idea of laying all your animations out declaratively on a flat timeline, and then you can scrub forward and backward through that timeline once it's been defined. Crucially, you are not allowed to modify that timeline as a side effect of scrubbing through it, otherwise it would break the ability to do deterministic scrubbing.

The solution is to dispatch both your animation, and the 2nd action from the same initial place, e.g.

function onClick() {
    dispatch(Animate({..., end_time: Date.now() + 1000}))
    setTimeout(() => {dispatch({some other action})}, 1000)
}

You could also do this with redux-thunk or another side-effect-handling redux library instead of setTimeout.