Skip to content

Commit 8673957

Browse files
committed
refactor: migrate TransitionGroup to TypeScript
1 parent c48efa9 commit 8673957

File tree

1 file changed

+49
-18
lines changed

1 file changed

+49
-18
lines changed

src/TransitionGroup.js renamed to src/TransitionGroup.tsx

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import PropTypes from 'prop-types';
22
import React from 'react';
3+
import type { ReactElement, ReactChild } from 'react';
34
import TransitionGroupContext from './TransitionGroupContext';
45

56
import {
@@ -8,11 +9,32 @@ import {
89
getNextChildMapping,
910
} from './utils/ChildMapping';
1011

11-
const values = Object.values || ((obj) => Object.keys(obj).map((k) => obj[k]));
12+
const values =
13+
Object.values ||
14+
((obj: Record<string, unknown>) => Object.keys(obj).map((k) => obj[k]));
1215

1316
const defaultProps = {
1417
component: 'div',
15-
childFactory: (child) => child,
18+
childFactory: (child: ReactElement) => child,
19+
};
20+
21+
type Props = {
22+
component: any;
23+
children: any;
24+
appear: boolean;
25+
enter: boolean;
26+
exit: boolean;
27+
childFactory: (child: ReactElement) => ReactElement;
28+
};
29+
30+
type State = {
31+
children: Record<string, ReactChild>;
32+
contextValue: { isMounting: boolean };
33+
handleExited: (
34+
child: ReactElement<{ onExited: (node: HTMLElement) => void }>,
35+
node: HTMLElement
36+
) => void;
37+
firstRender: boolean;
1638
};
1739

1840
/**
@@ -29,13 +51,17 @@ const defaultProps = {
2951
* component. This means you can mix and match animations across different list
3052
* items.
3153
*/
32-
class TransitionGroup extends React.Component {
33-
constructor(props, context) {
54+
class TransitionGroup extends React.Component<Props, State> {
55+
static defaultProps = defaultProps;
56+
57+
mounted = false;
58+
constructor(props: Props, context: any) {
3459
super(props, context);
3560

3661
const handleExited = this.handleExited.bind(this);
3762

3863
// Initial children should all be entering, dependent on appear
64+
// @ts-expect-error FIXME: Property 'children' is missing in type '{ contextValue: { isMounting: true; }; handleExited: (child: React.ReactElement<{ onExited: (node: HTMLElement) => void; }, string | React.JSXElementConstructor<any>>, node: HTMLElement) => void; firstRender: true; }' but required in type 'Readonly<State>'.ts(2741)
3965
this.state = {
4066
contextValue: { isMounting: true },
4167
handleExited,
@@ -55,8 +81,8 @@ class TransitionGroup extends React.Component {
5581
}
5682

5783
static getDerivedStateFromProps(
58-
nextProps,
59-
{ children: prevChildMapping, handleExited, firstRender }
84+
nextProps: Props,
85+
{ children: prevChildMapping, handleExited, firstRender }: State
6086
) {
6187
return {
6288
children: firstRender
@@ -67,10 +93,12 @@ class TransitionGroup extends React.Component {
6793
}
6894

6995
// node is `undefined` when user provided `nodeRef` prop
70-
handleExited(child, node) {
96+
handleExited(
97+
child: ReactElement<{ onExited: (node: HTMLElement) => void }>,
98+
node: HTMLElement
99+
) {
71100
let currentChildMapping = getChildMapping(this.props.children);
72-
73-
if (child.key in currentChildMapping) return;
101+
if (child.key && child.key in currentChildMapping) return;
74102

75103
if (child.props.onExited) {
76104
child.props.onExited(node);
@@ -79,8 +107,9 @@ class TransitionGroup extends React.Component {
79107
if (this.mounted) {
80108
this.setState((state) => {
81109
let children = { ...state.children };
82-
83-
delete children[child.key];
110+
if (child.key) {
111+
delete children[child.key];
112+
}
84113
return { children };
85114
});
86115
}
@@ -89,11 +118,14 @@ class TransitionGroup extends React.Component {
89118
render() {
90119
const { component: Component, childFactory, ...props } = this.props;
91120
const { contextValue } = this.state;
121+
// @ts-expect-error FIXME: Type 'undefined' is not assignable to type 'ReactElement<any, string | JSXElementConstructor<any>>'.ts(2345)
92122
const children = values(this.state.children).map(childFactory);
93-
94-
delete props.appear;
95-
delete props.enter;
96-
delete props.exit;
123+
const {
124+
appear: _appear,
125+
enter: _enter,
126+
exit: _exit,
127+
...delegatingProps
128+
} = props;
97129

98130
if (Component === null) {
99131
return (
@@ -104,12 +136,13 @@ class TransitionGroup extends React.Component {
104136
}
105137
return (
106138
<TransitionGroupContext.Provider value={contextValue}>
107-
<Component {...props}>{children}</Component>
139+
<Component {...delegatingProps}>{children}</Component>
108140
</TransitionGroupContext.Provider>
109141
);
110142
}
111143
}
112144

145+
// @ts-expect-error To make TS migration diffs minimum, I've left propTypes here instead of defining a static property
113146
TransitionGroup.propTypes = {
114147
/**
115148
* `<TransitionGroup>` renders a `<div>` by default. You can change this
@@ -166,6 +199,4 @@ TransitionGroup.propTypes = {
166199
childFactory: PropTypes.func,
167200
};
168201

169-
TransitionGroup.defaultProps = defaultProps;
170-
171202
export default TransitionGroup;

0 commit comments

Comments
 (0)