diff --git a/src/Transition.js b/src/Transition.js index 04e845db..95eb2990 100644 --- a/src/Transition.js +++ b/src/Transition.js @@ -5,6 +5,7 @@ import ReactDOM from 'react-dom'; import config from './config'; import { timeoutsShape } from './utils/PropTypes'; import TransitionGroupContext from './TransitionGroupContext'; +import { nextTick } from './utils/nextTick'; export const UNMOUNTED = 'unmounted'; export const EXITED = 'exited'; @@ -212,7 +213,15 @@ class Transition extends React.Component { this.cancelNextCallback(); if (nextStatus === ENTERING) { - this.performEnter(mounting); + // https://github.com/reactjs/react-transition-group/pull/749 + // With unmountOnExit or mountOnEnter, the enter animation should happen at the transition between `exited` and `entering`. + // To make the animation happen, we have to separate each rendering and avoid being processed as batched. + if (this.props.unmountOnExit || this.props.mountOnEnter) { + // `exited` -> `entering` + nextTick(() => this.performEnter(mounting)); + } else { + this.performEnter(mounting); + } } else { this.performExit(); } diff --git a/src/utils/nextTick.js b/src/utils/nextTick.js new file mode 100644 index 00000000..cc35f064 --- /dev/null +++ b/src/utils/nextTick.js @@ -0,0 +1,11 @@ +// polyfill for requestAnimationFrame +const rAF = + typeof window !== 'undefined' && + typeof window.requestAnimationFrame === 'function' + ? window.requestAnimationFrame + : (cb) => setTimeout(cb, 1); + +// https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame +// Note: Your callback routine must itself call requestAnimationFrame() again +// if you want to animate another frame at the next repaint. requestAnimationFrame() is 1 shot. +export const nextTick = (cb) => rAF(() => rAF(cb)); diff --git a/stories/CSSTransition.js b/stories/CSSTransition.js new file mode 100644 index 00000000..73379400 --- /dev/null +++ b/stories/CSSTransition.js @@ -0,0 +1,50 @@ +import React, { useState } from 'react'; +import { storiesOf } from '@storybook/react'; + +import StoryFixture from './StoryFixture'; +import Fade from './transitions/CSSFade'; + +function ToggleFixture({ defaultIn, description, children }) { + const [show, setShow] = useState(defaultIn || false); + + return ( + +
+ +
+ {React.cloneElement(children, { in: show })} +
+ ); +} + +storiesOf('CSSTransition', module) + .add('Fade', () => ( + + asaghasg asgasg + + )) + .add('Fade with appear', () => ( + + asaghasg asgasg + + )) + .add('Fade with mountOnEnter', () => { + return ( + + Fade with mountOnEnter + + ); + }) + .add('Fade with unmountOnExit', () => { + return ( + + Fade with unmountOnExit + + ); + }); diff --git a/stories/NestedTransition.js b/stories/NestedTransition.js index ab2bd15c..a6e5a499 100644 --- a/stories/NestedTransition.js +++ b/stories/NestedTransition.js @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import StoryFixture from './StoryFixture'; -import Fade from './transitions/Fade'; +import Fade from './transitions/CSSFadeForTransitionGroup'; import Scale from './transitions/Scale'; function FadeAndScale(props) { diff --git a/stories/Transition.js b/stories/Transition.js index 4dea7e7d..da050927 100644 --- a/stories/Transition.js +++ b/stories/Transition.js @@ -60,4 +60,18 @@ storiesOf('Transition', module) Fade using innerRef ); + }) + .add('Fade with mountOnEnter', () => { + return ( + + Fade with mountOnEnter + + ); + }) + .add('Fade with unmountOnExit', () => { + return ( + + Fade with unmountOnExit + + ); }); diff --git a/stories/TransitionGroup.js b/stories/TransitionGroup.js index 0de07872..12f9d1a5 100644 --- a/stories/TransitionGroup.js +++ b/stories/TransitionGroup.js @@ -6,7 +6,7 @@ import TransitionGroup from '../src/TransitionGroup'; import CSSTransitionGroupFixture from './CSSTransitionGroupFixture'; import NestedTransition from './NestedTransition'; import StoryFixture from './StoryFixture'; -import Fade, { FADE_TIMEOUT } from './transitions/Fade'; +import Fade, { FADE_TIMEOUT } from './transitions/CSSFadeForTransitionGroup'; storiesOf('Css Transition Group', module) .add('Animates on all', () => ( diff --git a/stories/index.js b/stories/index.js index f038fac2..535c80a5 100644 --- a/stories/index.js +++ b/stories/index.js @@ -1,3 +1,4 @@ import './Transition'; +import './CSSTransition'; import './TransitionGroup'; import './ReplaceTransition'; diff --git a/stories/transitions/Bootstrap.js b/stories/transitions/Bootstrap.js index 8c2a120d..8c84949b 100644 --- a/stories/transitions/Bootstrap.js +++ b/stories/transitions/Bootstrap.js @@ -12,7 +12,7 @@ import Transition, { const styles = css` .fade { opacity: 0; - transition: opacity 0.15s linear; + transition: opacity 0.5s linear; } .fade.in { opacity: 1; @@ -47,7 +47,7 @@ export function Fade(props) { {...props} nodeRef={nodeRef} className={styles.fade} - timeout={150} + timeout={500} > {(status) => (
+
+ {props.children} +
+ + ); +} + +Fade.defaultProps = defaultProps; + +export default Fade; diff --git a/stories/transitions/Fade.js b/stories/transitions/CSSFadeForTransitionGroup.js similarity index 100% rename from stories/transitions/Fade.js rename to stories/transitions/CSSFadeForTransitionGroup.js