chenglou / react-motion

A spring that solves your animation problems.
MIT License
21.68k stars 1.15k forks source link

Single Element Enter & Leaving Animation on Prop Change (working, need improvement) #564

Closed PedroBern closed 5 years ago

PedroBern commented 5 years ago

Motivation

I wrote a wrapper for <Motion/>, where it gets easy to build enter and leaving animations on single elements when prop changes.

It remember the prevProps and when the component receive newProps, it will render with prevProps, make the leaving animations, render again with newProps on enter animation.

-> component receive newProps
-> render with prevProps and execute leaving animation
-> render with newProps and execute enter animation

Related idea from #311 but different implementation I guess.

Code

class MotionWrapper extends React.PureComponent {
  state = {
    open: true,
    prevChildren: this.props.children,
    motionKey: this.props.motionKey,
  };

  static getDerivedStateFromProps(props, state) {
    if (props.motionKey !== state.motionKey){
      return { open: false, };
    }
    else return null
  }

  componentDidUpdate = () => {
    if(!this.state.open){
      setTimeout(() => {
        return this.setState({
          open: true,
          prevChildren: this.props.children,
          motionKey: this.props.motionKey,
        })
      }, this.props.timeout);
    }
  }

  render() {

    const {
      motionKey,
      children,
      enterStyle,
      defaultEnterStyle,
      leaveStyle,
      defaultLeaveStyle
    } = this.props;

    const {
      open,
      prevChildren
    } = this.state;

    return (

      <React.Fragment>

        {open === true ?

          // Enter component
          <Motion
            key={motionKey}
            defaultStyle={defaultEnterStyle}
            style={enterStyle}
          >
            { children }
          </Motion>

          :

          // Leave component
          <Motion
            key={motionKey}
            defaultStyle={defaultLeaveStyle}
            style={leaveStyle}
          >
            { prevChildren }
          </Motion>

        }

      </React.Fragment>

    );
  }
}

MotionWrapper.defaultProps = {
  timeout: 500,
};

MotionWrapper.propTypes = {
  children: PropTypes.func.isRequired,
  motionKey: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
  ]).isRequired,
  defaultEnterStyle: PropTypes.object.isRequired,
  enterStyle: PropTypes.object.isRequired,
  defaultLeaveStyle: PropTypes.object.isRequired,
  leaveStyle: PropTypes.object.isRequired,
};

export default MotionWrapper;

Usage

import {spring} from 'react-motion'; // probably you will use spring...
import MotionWrapper from 'path/to/MotionWrapper"';

<MotionWrapper 
    motionKey={...}
    defaultEnterStyle={...}
    enterStyle={...}
    defaultLeaveStyle={...}
    leaveStyle={...}
>
   {interpolatingStyle => <div style={interpolatingStyle} />} //same children as in regular <Motion />
</MotionWrapper>

Improvement need

Rather then call setState inside a setTimeout, would be a lot better to call a callback in onRest when open === false and call setState inside it.

The problem was that I couldnt get it to rerender after call setState inside it.

like the problem in #322

like this:

...

// remove:
// componentDidUpdate = () => {
//    ...
// }

...

// add:
hadleEndLeave = () => {
    this.setState({
        open: true,
        prevChildren: this.props.children,
        motionKey: this.props.motionKey,
    })
    // do update state, but component dont rerender
}
...

...
// Leave component
<Motion
    key={motionKey}
    defaultStyle={defaultLeaveStyle}
    style={leaveStyle}
   onRest={() => this.handleEndLeave()}
>
    { prevChildren }
</Motion>
...

it actually will work if remove motionKey from this leaving component, but will be a big delay between end animation and setState, I didnt understand!

...
// Leave component
<Motion
    // key={motionKey}  **removing motionKey will work, but delay a lot to call onRest**
   ...

Any ideas?

Update

Did find out that the delay was caused by precision 0.1, increasing precision, there is no delay.