Skip to content

Commit 51bdceb

Browse files
authored
fix: enter animations with mountOnEnter or unmountOnExit (#749)
* fix: enter animation with mountOnEnter or unmountOnExit * chore: add stories for mountOnEnter or unmountOnExit * chore: apply prettier * Update stories/CSSTransition.js
1 parent 6cbd6aa commit 51bdceb

10 files changed

+143
-5
lines changed

src/Transition.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import ReactDOM from 'react-dom';
55
import config from './config';
66
import { timeoutsShape } from './utils/PropTypes';
77
import TransitionGroupContext from './TransitionGroupContext';
8+
import { nextTick } from './utils/nextTick';
89

910
export const UNMOUNTED = 'unmounted';
1011
export const EXITED = 'exited';
@@ -212,7 +213,15 @@ class Transition extends React.Component {
212213
this.cancelNextCallback();
213214

214215
if (nextStatus === ENTERING) {
215-
this.performEnter(mounting);
216+
// https://github.com/reactjs/react-transition-group/pull/749
217+
// With unmountOnExit or mountOnEnter, the enter animation should happen at the transition between `exited` and `entering`.
218+
// To make the animation happen, we have to separate each rendering and avoid being processed as batched.
219+
if (this.props.unmountOnExit || this.props.mountOnEnter) {
220+
// `exited` -> `entering`
221+
nextTick(() => this.performEnter(mounting));
222+
} else {
223+
this.performEnter(mounting);
224+
}
216225
} else {
217226
this.performExit();
218227
}

src/utils/nextTick.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// polyfill for requestAnimationFrame
2+
const rAF =
3+
typeof window !== 'undefined' &&
4+
typeof window.requestAnimationFrame === 'function'
5+
? window.requestAnimationFrame
6+
: (cb) => setTimeout(cb, 1);
7+
8+
// https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
9+
// Note: Your callback routine must itself call requestAnimationFrame() again
10+
// if you want to animate another frame at the next repaint. requestAnimationFrame() is 1 shot.
11+
export const nextTick = (cb) => rAF(() => rAF(cb));

stories/CSSTransition.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React, { useState } from 'react';
2+
import { storiesOf } from '@storybook/react';
3+
4+
import StoryFixture from './StoryFixture';
5+
import Fade from './transitions/CSSFade';
6+
7+
function ToggleFixture({ defaultIn, description, children }) {
8+
const [show, setShow] = useState(defaultIn || false);
9+
10+
return (
11+
<StoryFixture description={description}>
12+
<div style={{ marginBottom: 10 }}>
13+
<button
14+
onClick={() => {
15+
setShow(!show);
16+
}}
17+
>
18+
Toggle
19+
</button>
20+
</div>
21+
{React.cloneElement(children, { in: show })}
22+
</StoryFixture>
23+
);
24+
}
25+
26+
storiesOf('CSSTransition', module)
27+
.add('Fade', () => (
28+
<ToggleFixture>
29+
<Fade>asaghasg asgasg</Fade>
30+
</ToggleFixture>
31+
))
32+
.add('Fade with appear', () => (
33+
<ToggleFixture defaultIn>
34+
<Fade appear>asaghasg asgasg</Fade>
35+
</ToggleFixture>
36+
))
37+
.add('Fade with mountOnEnter', () => {
38+
return (
39+
<ToggleFixture>
40+
<Fade mountOnEnter>Fade with mountOnEnter</Fade>
41+
</ToggleFixture>
42+
);
43+
})
44+
.add('Fade with unmountOnExit', () => {
45+
return (
46+
<ToggleFixture>
47+
<Fade unmountOnExit>Fade with unmountOnExit</Fade>
48+
</ToggleFixture>
49+
);
50+
});

stories/NestedTransition.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState } from 'react';
22

33
import StoryFixture from './StoryFixture';
4-
import Fade from './transitions/Fade';
4+
import Fade from './transitions/CSSFadeForTransitionGroup';
55
import Scale from './transitions/Scale';
66

77
function FadeAndScale(props) {

stories/Transition.js

+14
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,18 @@ storiesOf('Transition', module)
6060
<FadeInnerRef innerRef={nodeRef}>Fade using innerRef</FadeInnerRef>
6161
</ToggleFixture>
6262
);
63+
})
64+
.add('Fade with mountOnEnter', () => {
65+
return (
66+
<ToggleFixture>
67+
<Fade mountOnEnter>Fade with mountOnEnter</Fade>
68+
</ToggleFixture>
69+
);
70+
})
71+
.add('Fade with unmountOnExit', () => {
72+
return (
73+
<ToggleFixture>
74+
<Fade unmountOnExit>Fade with unmountOnExit</Fade>
75+
</ToggleFixture>
76+
);
6377
});

stories/TransitionGroup.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import TransitionGroup from '../src/TransitionGroup';
66
import CSSTransitionGroupFixture from './CSSTransitionGroupFixture';
77
import NestedTransition from './NestedTransition';
88
import StoryFixture from './StoryFixture';
9-
import Fade, { FADE_TIMEOUT } from './transitions/Fade';
9+
import Fade, { FADE_TIMEOUT } from './transitions/CSSFadeForTransitionGroup';
1010

1111
storiesOf('Css Transition Group', module)
1212
.add('Animates on all', () => (

stories/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import './Transition';
2+
import './CSSTransition';
23
import './TransitionGroup';
34
import './ReplaceTransition';

stories/transitions/Bootstrap.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import Transition, {
1212
const styles = css`
1313
.fade {
1414
opacity: 0;
15-
transition: opacity 0.15s linear;
15+
transition: opacity 0.5s linear;
1616
}
1717
.fade.in {
1818
opacity: 1;
@@ -47,7 +47,7 @@ export function Fade(props) {
4747
{...props}
4848
nodeRef={nodeRef}
4949
className={styles.fade}
50-
timeout={150}
50+
timeout={500}
5151
>
5252
{(status) => (
5353
<div

stories/transitions/CSSFade.js

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { css } from 'astroturf';
2+
import React, { useRef } from 'react';
3+
4+
import CSSTransition from '../../src/CSSTransition';
5+
6+
export const FADE_TIMEOUT = 1000;
7+
8+
const styles = css`
9+
.default {
10+
opacity: 0;
11+
}
12+
.enter-done {
13+
opacity: 1;
14+
}
15+
16+
.enter,
17+
.appear {
18+
opacity: 0.01;
19+
}
20+
21+
.enter.enter-active,
22+
.appear.appear-active {
23+
opacity: 1;
24+
transition: opacity ${FADE_TIMEOUT}ms ease-in;
25+
}
26+
27+
.exit {
28+
opacity: 1;
29+
}
30+
.exit.exit-active {
31+
opacity: 0.01;
32+
transition: opacity ${0.8 * FADE_TIMEOUT}ms ease-in;
33+
}
34+
`;
35+
36+
const defaultProps = {
37+
in: false,
38+
timeout: FADE_TIMEOUT,
39+
};
40+
function Fade(props) {
41+
const nodeRef = useRef();
42+
return (
43+
<CSSTransition {...props} classNames={styles} nodeRef={nodeRef}>
44+
<div ref={nodeRef} className={styles.default}>
45+
{props.children}
46+
</div>
47+
</CSSTransition>
48+
);
49+
}
50+
51+
Fade.defaultProps = defaultProps;
52+
53+
export default Fade;

0 commit comments

Comments
 (0)