Skip to content

Commit

Permalink
fix: enter animations with mountOnEnter or unmountOnExit (#749)
Browse files Browse the repository at this point in the history
* fix: enter animation with mountOnEnter or unmountOnExit

* chore: add stories for mountOnEnter or unmountOnExit

* chore: apply prettier

* Update stories/CSSTransition.js
  • Loading branch information
koba04 authored Jul 30, 2022
1 parent 6cbd6aa commit 51bdceb
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 5 deletions.
11 changes: 10 additions & 1 deletion src/Transition.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
}
Expand Down
11 changes: 11 additions & 0 deletions src/utils/nextTick.js
Original file line number Diff line number Diff line change
@@ -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));
50 changes: 50 additions & 0 deletions stories/CSSTransition.js
Original file line number Diff line number Diff line change
@@ -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 (
<StoryFixture description={description}>
<div style={{ marginBottom: 10 }}>
<button
onClick={() => {
setShow(!show);
}}
>
Toggle
</button>
</div>
{React.cloneElement(children, { in: show })}
</StoryFixture>
);
}

storiesOf('CSSTransition', module)
.add('Fade', () => (
<ToggleFixture>
<Fade>asaghasg asgasg</Fade>
</ToggleFixture>
))
.add('Fade with appear', () => (
<ToggleFixture defaultIn>
<Fade appear>asaghasg asgasg</Fade>
</ToggleFixture>
))
.add('Fade with mountOnEnter', () => {
return (
<ToggleFixture>
<Fade mountOnEnter>Fade with mountOnEnter</Fade>
</ToggleFixture>
);
})
.add('Fade with unmountOnExit', () => {
return (
<ToggleFixture>
<Fade unmountOnExit>Fade with unmountOnExit</Fade>
</ToggleFixture>
);
});
2 changes: 1 addition & 1 deletion stories/NestedTransition.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
14 changes: 14 additions & 0 deletions stories/Transition.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,18 @@ storiesOf('Transition', module)
<FadeInnerRef innerRef={nodeRef}>Fade using innerRef</FadeInnerRef>
</ToggleFixture>
);
})
.add('Fade with mountOnEnter', () => {
return (
<ToggleFixture>
<Fade mountOnEnter>Fade with mountOnEnter</Fade>
</ToggleFixture>
);
})
.add('Fade with unmountOnExit', () => {
return (
<ToggleFixture>
<Fade unmountOnExit>Fade with unmountOnExit</Fade>
</ToggleFixture>
);
});
2 changes: 1 addition & 1 deletion stories/TransitionGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => (
Expand Down
1 change: 1 addition & 0 deletions stories/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import './Transition';
import './CSSTransition';
import './TransitionGroup';
import './ReplaceTransition';
4 changes: 2 additions & 2 deletions stories/transitions/Bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -47,7 +47,7 @@ export function Fade(props) {
{...props}
nodeRef={nodeRef}
className={styles.fade}
timeout={150}
timeout={500}
>
{(status) => (
<div
Expand Down
53 changes: 53 additions & 0 deletions stories/transitions/CSSFade.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { css } from 'astroturf';
import React, { useRef } from 'react';

import CSSTransition from '../../src/CSSTransition';

export const FADE_TIMEOUT = 1000;

const styles = css`
.default {
opacity: 0;
}
.enter-done {
opacity: 1;
}
.enter,
.appear {
opacity: 0.01;
}
.enter.enter-active,
.appear.appear-active {
opacity: 1;
transition: opacity ${FADE_TIMEOUT}ms ease-in;
}
.exit {
opacity: 1;
}
.exit.exit-active {
opacity: 0.01;
transition: opacity ${0.8 * FADE_TIMEOUT}ms ease-in;
}
`;

const defaultProps = {
in: false,
timeout: FADE_TIMEOUT,
};
function Fade(props) {
const nodeRef = useRef();
return (
<CSSTransition {...props} classNames={styles} nodeRef={nodeRef}>
<div ref={nodeRef} className={styles.default}>
{props.children}
</div>
</CSSTransition>
);
}

Fade.defaultProps = defaultProps;

export default Fade;
File renamed without changes.

0 comments on commit 51bdceb

Please sign in to comment.