TransitionGroup with react and redux: replace old element with animation

336 views Asked by At

Consider a use case: a block with a text inside (text is fetched from store). When text changes - block smoothly goes away and the other block appears.

Pseudo code for better illustration:

import TransitionGroup from 'react-addons-transition-group'

@connect((state) => ({text: state.text}))
class Container extends React.Component {
  render() {
    return (
      <div>
        <TransitionGroup>
          <Block key={this.props.text}/> // change block when text changes
        </TransitionGroup>
      </div>
    )
  }
}

@TransitionWrapper() // pass componentWillEnter through wrapper
@connect((state) => ({text: state.text}), null, null, {withRef: true})
class Block extends React.Component {
componentWillEnter(callback) {
    // fancy animations!!!
    const el = ReactDOM.findDOMNode(this); 
    TweenMax.fromTo(el, 1, { 
      alpha: 0, 
    }, { 
      alpha: 1, 
      onComplete: callback 
    }); 
  }

  componentWillLeave (callback) { 
    const el = ReactDOM.findDOMNode(this); 
    TweenMax.to(el, 1, { 
      alpha: 0, 
      onComplete: callback 
    }); 
  }

  render() {
     return (
       <div>{this.props.text}</div>
     )
   }
}

What happens when state.text changes?

  1. New Block appears, because key is changed; componentWillEnter starts the animation for it. Great.
  2. Old block gets re-rendered and componentWillLeave starts the animation for it.
  3. When first animation finishes re-render happens again.

The issue is the step no 2: old element should disappear with the old data, but due to re-render it changes his content to a new one from store, so user see this:

  1. store.text = 'Foo'. User see one Block with text 'Foo' on the screen.
  2. store.text = 'Bar'. User see two Blocks, both with text 'Bar' on the screen. One block is disappearing.
  3. Animation finishes, user see one Block with text Foo on screen.

I believe using transitions is pretty common nowadays and this should be a common issue, but I was surprised I couldn't find anything related. Best idea I can think is to "freeze" props on the element when it's about to leave (or passing previous store, so it re-renders with previous data), but it feels hacky to me.

What's the best way to solve this problem?

1

There are 1 answers

0
Kevin Li On

We met the same problem with redux store, since when data got removed, the props contains nothing, thus, the UI will show no data when unmounting animation is happening.

I think it is hacky to use the old store or state (break the convention of React life Cycle), you can use loading placeholder if no data is available, like

if (!this.props.text){
   return <EmptyPlaceholder />
}

also the animation duration is small like 300 milliseconds, the user experience won't be bad.

Alternatively, you need to define a class instance variable like:

componentWillMount(){
       if(this.props.text){
         this.text = this.prop.text;  
       }
    }

Then render text like

<Block key={this.props.text || this.text}/>

Then the old text will always be there when unmounting animation happened. I tested on my project, it worked very well. Hopefully it will help u, if not please feel free to msg me.