// @flow import React, { type Node } from 'react'; import type { InOutAnimationMode } from '../../types'; export type AnimateProvided = {| onClose: () => void, animate: InOutAnimationMode, data: mixed, |}; type Props = {| on: mixed, shouldAnimate: boolean, children: (provided: AnimateProvided) => Node, |}; type State = {| data: mixed, isVisible: boolean, animate: InOutAnimationMode, |}; // Using a class here rather than hooks because // getDerivedStateFromProps results in far less renders. // Using hooks to implement this was quite messy and resulted in lots of additional renders export default class AnimateInOut extends React.PureComponent { state: State = { isVisible: Boolean(this.props.on), data: this.props.on, // not allowing to animate close on mount animate: this.props.shouldAnimate && this.props.on ? 'open' : 'none', }; static getDerivedStateFromProps(props: Props, state: State): State { if (!props.shouldAnimate) { return { isVisible: Boolean(props.on), data: props.on, animate: 'none', }; } // need to animate in if (props.on) { return { isVisible: true, // have new data to animate in with data: props.on, animate: 'open', }; } // need to animate out if there was data if (state.isVisible) { return { isVisible: true, // use old data for animating out data: state.data, animate: 'close', }; } // close animation no longer visible return { isVisible: false, animate: 'close', data: null, }; } onClose = () => { if (this.state.animate !== 'close') { return; } this.setState({ isVisible: false, }); }; render() { if (!this.state.isVisible) { return null; } const provided: AnimateProvided = { onClose: this.onClose, data: this.state.data, animate: this.state.animate, }; return this.props.children(provided); } }