Skip to content

Commit 7c449d2

Browse files
committed
fix: fix Transition component & add Counter example
1 parent efe853a commit 7c449d2

10 files changed

+81
-42
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-transitioning",
3-
"version": "1.0.3",
3+
"version": "1.0.4",
44
"description": "React components for easily implementing basic CSS animations and transitions",
55
"main": "dist/index.cjs.js",
66
"module": "dist/index.esm.js",

src/Transition.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export type TransitionProps = {
107107
};
108108

109109
const EVENT_MAP: {
110-
[key in TransitionPhase]: [TransitionPhaseEvent, TransitionPhase?, boolean?];
110+
[key in TransitionPhase]: [eventName: TransitionPhaseEvent, nextPhase?: TransitionPhase, delay?: boolean];
111111
} = {
112112
[TransitionPhase.APPEAR]: [TransitionPhaseEvent.ENTER, TransitionPhase.APPEAR_ACTIVE],
113113
[TransitionPhase.APPEAR_ACTIVE]: [TransitionPhaseEvent.ENTERING, TransitionPhase.APPEAR_DONE, true],
@@ -159,6 +159,7 @@ export function Transition(props: TransitionProps) {
159159
const { [eventName]: event } = props;
160160
event?.();
161161
let tm = 0;
162+
let af = 0;
162163
if (nextPhase) {
163164
if (delay) {
164165
if (addEndListener) {
@@ -167,11 +168,12 @@ export function Transition(props: TransitionProps) {
167168
tm = setTimeout(setPhase, duration, nextPhase);
168169
}
169170
} else {
170-
tm = setTimeout(setPhase, 0, nextPhase);
171+
af = requestAnimationFrame(() => setPhase(nextPhase));
171172
}
172173
}
173174
return () => {
174175
clearTimeout(tm);
176+
cancelAnimationFrame(af);
175177
};
176178
}, [phase, duration]);
177179

src/TransitionGroup.tsx

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,6 @@ type VisibleElement = {
4141
removeTimeout?: number;
4242
};
4343

44-
const getChildProp = <T,>(child: React.ReactElement<any>, propName: string, defaultValue: T): T => {
45-
const { [propName]: prop = defaultValue } = child.props;
46-
return prop;
47-
};
48-
4944
/**
5045
* The `TransitionGroup` component handles a collection of `Transition` child elements
5146
* and applies transition animations when elements enter and exit the DOM.
@@ -69,37 +64,32 @@ export function TransitionGroup(props: TransitionGroupProps) {
6964
}
7065
});
7166

72-
const pushVisibleElement = (
67+
const addVisibleElement = (
7368
element: VisibleElement['element'],
7469
removeTimeout?: VisibleElement['removeTimeout'],
7570
) => {
7671
const elementClone = cloneElement(element, {
77-
exit,
78-
enter,
79-
duration,
8072
in: !removeTimeout,
81-
appear: firstRenderRef.current
82-
? getChildProp(element, 'appear', appear)
83-
: getChildProp(element, 'enter', enter),
73+
enter: false,
74+
appear: firstRenderRef.current ? (element.props.appear ?? appear) : (element.props.enter ?? enter),
75+
exit: element.props.exit ?? exit,
76+
duration: element.props.duration ?? duration,
8477
});
8578

8679
nextVisibleElements.push({ element, removeTimeout });
8780
nextElements.push(elementClone);
8881
};
8982

9083
const makeRemoveTimeout = (elementToRemove: VisibleElement['element']) =>
91-
window.setTimeout(
92-
() => {
93-
const { current: prevVisibleChildren } = prevVisibleElementsRef;
94-
const indexToDelete = prevVisibleChildren.findIndex(
95-
({ element }) => element.key === elementToRemove.key,
96-
);
97-
if (indexToDelete >= 0) {
98-
prevVisibleChildren.splice(indexToDelete, 1);
99-
}
100-
},
101-
getChildProp(elementToRemove, 'duration', duration),
102-
);
84+
window.setTimeout(() => {
85+
const { current: prevVisibleChildren } = prevVisibleElementsRef;
86+
const indexToDelete = prevVisibleChildren.findIndex(
87+
({ element }) => element.key === elementToRemove.key,
88+
);
89+
if (indexToDelete >= 0) {
90+
prevVisibleChildren.splice(indexToDelete, 1);
91+
}
92+
}, elementToRemove.props.duration ?? duration);
10393

10494
let lastAddedElementIndex = 0;
10595

@@ -111,12 +101,12 @@ export function TransitionGroup(props: TransitionGroupProps) {
111101
if (foundIndex < 0) {
112102
// The visible element already has a removal timeout, which means it's currently exiting
113103
if (prevRemoveTimeout) {
114-
pushVisibleElement(prevElement, prevRemoveTimeout);
104+
addVisibleElement(prevElement, prevRemoveTimeout);
115105
} else {
116106
// Start the removal timeout, but continue rendering this element
117-
const shouldAddTimeout = exit && prevElement.props.exit !== false;
107+
const shouldAddTimeout = prevElement.props.exit ?? exit;
118108
if (shouldAddTimeout) {
119-
pushVisibleElement(prevElement, makeRemoveTimeout(prevElement));
109+
addVisibleElement(prevElement, makeRemoveTimeout(prevElement));
120110
}
121111
}
122112
} else {
@@ -126,7 +116,7 @@ export function TransitionGroup(props: TransitionGroupProps) {
126116
}
127117
// Add derived element along with all previous children
128118
for (let i = lastAddedElementIndex; i <= foundIndex; i += 1) {
129-
pushVisibleElement(derivedElements[i]);
119+
addVisibleElement(derivedElements[i]);
130120
}
131121
}
132122
// Save the index to loop only through the remaining element
@@ -135,7 +125,7 @@ export function TransitionGroup(props: TransitionGroupProps) {
135125

136126
// Add the remaining elements
137127
for (let i = lastAddedElementIndex; i < derivedElements.length; i += 1) {
138-
pushVisibleElement(derivedElements[i]);
128+
addVisibleElement(derivedElements[i]);
139129
}
140130

141131
// Save the visible elements

stories/examples/BasicTransition.stories.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import { Transition } from 'react-transitioning';
44

55
const META: Meta = {
66
tags: ['!dev'],
7-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
8-
render: args => {
7+
render: () => {
98
const [visible, setVisible] = useState(true);
109
const [alwaysMounted, setAlwaysMounted] = useState(true);
1110
const toggleVisibility = () => setVisible(!visible);

stories/examples/ConditionalRender.stories.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import { FadeTransition } from '../misc/StyleFadeTransition';
55

66
const META: Meta = {
77
tags: ['!dev'],
8-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
9-
render: args => {
8+
render: () => {
109
const [showFirstItem, setShowFirstItem] = useState(true);
1110
const [showSecondItem, setShowSecondItem] = useState(true);
1211
const [showThirdItem, setShowThirdItem] = useState(true);

stories/examples/Counter.mdx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Canvas, Meta, Source } from '@storybook/blocks';
2+
import * as Stories from './Counter.stories';
3+
4+
<Meta title="Examples/Transition Group/Basic Counter" />
5+
6+
# Transition Group Example: Counter
7+
8+
This example demonstrates how to use the `TransitionGroup` component to animate a counter as it updates. Each change in the counter triggers a smooth transition.
9+
10+
## Live Demo
11+
12+
Check out the live demo below to see the `TransitionGroup` in action:
13+
14+
<Canvas of={Stories.Default} />
15+
16+
## Source Code
17+
18+
The source code for this example is available on [GitHub](https://github.com/fakundo/react-transitioning/blob/main/stories/examples/Counter.stories.tsx).
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Meta, StoryObj } from '@storybook/react';
2+
import { TransitionGroup } from 'react-transitioning';
3+
import { useEffect, useState } from 'react';
4+
import { FadeTransition } from '../misc/StyleFadeTransition';
5+
6+
const duration = 1000;
7+
8+
const META: Meta = {
9+
tags: ['!dev'],
10+
render: () => {
11+
const [sec, setSec] = useState(0);
12+
13+
useEffect(() => {
14+
const i = setInterval(() => {
15+
setSec(prevSec => prevSec + 1);
16+
}, duration);
17+
return () => {
18+
clearInterval(i);
19+
};
20+
}, []);
21+
22+
return (
23+
<TransitionGroup duration={duration}>
24+
<FadeTransition key={sec}>
25+
<div style={{ fontSize: 60, fontFamily: 'monospace' }}>{sec}</div>
26+
</FadeTransition>
27+
</TransitionGroup>
28+
);
29+
},
30+
};
31+
32+
export default META;
33+
34+
export const Default: StoryObj<typeof META> = {};

stories/examples/FadeTransitionClassNames.stories.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import { FadeTransition } from '../misc/CSSFadeTransition';
44

55
const META: Meta = {
66
tags: ['!dev'],
7-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
8-
render: args => {
7+
render: () => {
98
const [visible, setVisible] = useState(true);
109
return (
1110
<>

stories/examples/FadeTransitionStyles.stories.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import { FadeTransition } from '../misc/StyleFadeTransition';
44

55
const META: Meta = {
66
tags: ['!dev'],
7-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
8-
render: args => {
7+
render: () => {
98
const [visible, setVisible] = useState(true);
109
return (
1110
<>

stories/examples/ItemList.stories.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ const makeItem = () => `ITEM:${Math.random().toString().substring(2, 8)}`;
77

88
const META: Meta = {
99
tags: ['!dev'],
10-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
11-
render: args => {
10+
render: () => {
1211
const [shouldAnimateShuffle, setShouldAnimateShuffle] = useState(false);
1312
const [items, setItems] = useState(() => new Array(10).fill('').map(makeItem));
1413

0 commit comments

Comments
 (0)