Redux/Flux (with ReactJS) and animation
Asked Answered
H

2

7

I'm learning React+Redux and I don't understand the proper way of doing the animations. Lets speak by example:

For instance, I have a list and I would like to remove items on click. That's super easy if I have no animation effects there: dispatch REMOVE_ITEM action on click, reducer removes the item from the store and react re-renders html.

Let's add an animation of deleting the line item on click. So, when user clicks on an item I want to run a fancy effect of line item removal and... how? I can think of several ways how to do it:

1) On click I dispatch REMOVE_ITEM action, then reducer mark an item as goingToBeDeleted in Store, then react renders that element with a class of .fancy-dissolve-animation and I run a timer to dispatch the second action REMOVE_ITEM_COMPLETED. I don't like this idea, because it's still unclear how to add JS animations here (for example, with TweenMax) and I run a JS timer to re-render when CSS animation ends. Doesn't sound good.

2) I dispatch ITEM_REMOVE_PROGRESS actions with an interval of ~30ms, and store holds some "value" which represents the current state of animation. I don't like it too, as it would require me to copy the store ~120 times for ~2 seconds of animation (say, I want smooth 60 fps animation) and that's simply a waste of memory.

3) Make an animation and dispatch REMOVE_ITEM only after animation finishes. That's the most appropriate way I can think of, but still I'd like to have things changed in store right after user makes the action. For example, animation may take longer than few seconds and REMOVE_ITEM might sync with a backend – there's no reason to wait animation finish to make a backend API call.

Thanks for reading – any suggestions?

Hellgrammite answered 13/10, 2016 at 10:21 Comment(2)
Animation which have no data influence should be outside redux store.Paragrapher
@MaciejSikora yes, totally agree here - that's how I feel it should be. But still I have no idea what is the good way to achieve that. :(Hellgrammite
U
4

React has a great solution to this problem in the ReactCSSTransitionGroup helper class (see https://facebook.github.io/react/docs/animation.html). With this option, React takes care of it for you, by keeping the DOM state for the child as it was at the last render. You simply wrap your items in a ReactCSSTransitionGroup object. This keeps track of its children, and when it is rendered with a child removed, instead of rendering without the child, it renders with the child, but adds a CSS class to the child (which you can use to trigger a CSS animation, or you can just use CSS transitions for simplicity). Then, after a timeout (configured as a prop passed to ReactCSSTransitionGroup), it will re-render again, with the child removed from the DOM.

To use ReactCSSTransitionGroup, you'll need to npm install react-addons-css-transition-group, and then require/import 'react-addons-css-transition-group'. The animation docs give more detailed information.

One thing to remember - make sure the children have unique, unchanging keys. Just using the index as the key will make it behave incorrectly.

Underwater answered 14/10, 2016 at 13:20 Comment(2)
Great, that's closer to what I'm looking for. Will try soon, many thanks!Hellgrammite
Thank you, exactly what I need! Sorry for the late reply though :PHellgrammite
P
2

Instant actions are problematic in redux which saves state, so if we send action and it will change store then this change in store is available in next states, so We can have situation where animation is showing over and over because in store such parameter was set.

My solution for redux instant actions is to add some id like ( Action example code ):

{
    type:"SOME_ANIMATION",
    id: new Date().getTime() //we get timestamp of animation init
}

Next in component which runs animations save last animation id and if its match don't do animation. I use component state so for example ( Component code):

componentDidUpdate (){

if (this.lastAnimationId===this.props.animation.id)
return; //the same animation id so do not do anything

//here setState or do animation because it is new one

this.lastAnimationId=this.props.animation.id; //here set new id of last abnimation

}

Thanks id we can have only one action without actions which are reversing the state. Reversing actions after timeout can cause problems because if other action ( which is connected with component ) will be send before reverse action then animation can start again.

Minuses of proposed by me approach are that animation data exists in state, but exists also animation id which give us information about it. So we can say that store saves last dispatched animation.

Paragrapher answered 13/10, 2016 at 10:41 Comment(5)
Thanks for the solution. And who is responsible for the dispatching ANIMATION_FINISHED action? The animation itself?Hellgrammite
In this solution You need no such action. I described it in text about reverse actions. It is instant action with id, so it stays in state.Paragrapher
Oh, I see what you mean now. Unfortunately, I still cannot clearly tell how this can help with a delete action. By using your solution I would have to mark items as "removed" + adding them animation ID. While this could work it's still not very good as I will have removed items in store :(Hellgrammite
If you want to run animation on action then it is no reason to delete it from store. If on animation id like I showed is all You need. Action was sended to store, animation runs, if state changes but we have the same animation id then that means it is old one.Paragrapher
... making the deleted component to re-render again, even though it would be invisible. I see what you are talking about, but I hoped for a cleaner solution here. Thanks - I'll wait a couple days for any other ideas and if nothing good comes out - I'll go with your answerHellgrammite

© 2022 - 2024 — McMap. All rights reserved.