@@ -2,64 +2,66 @@ import _ from 'lodash'
2
2
import PropTypes from 'prop-types'
3
3
import React from 'react'
4
4
5
- import { getElementType , getUnhandledProps , makeDebugger , SUI } from '../../lib'
5
+ import { getElementType , getUnhandledProps , makeDebugger , SUI , useEventCallback } from '../../lib'
6
6
import { getChildMapping , mergeChildMappings } from './utils/childMapping'
7
7
import wrapChild from './utils/wrapChild'
8
8
9
9
const debug = makeDebugger ( 'transition_group' )
10
10
11
11
/**
12
- * A Transition.Group animates children as they mount and unmount.
12
+ * Wraps all children elements with proper callbacks and props.
13
+ *
14
+ * @param {React.ReactNode } children
15
+ * @param {Stream } animation
16
+ * @param {Number|String|Object } duration
17
+ * @param {Boolean } directional
18
+ *
19
+ * @return {Object }
13
20
*/
14
- export default class TransitionGroup extends React . Component {
15
- state = {
16
- // Keeping a callback under the state is a hack to make it accessible under getDerivedStateFromProps()
17
- handleOnHide : ( nothing , childProps ) => {
18
- debug ( 'handleOnHide' , childProps )
19
- const { reactKey } = childProps
20
-
21
- this . setState ( ( state ) => {
22
- const children = { ...state . children }
23
- delete children [ reactKey ]
24
-
25
- return { children }
26
- } )
27
- } ,
28
- }
21
+ function useWrappedChildren ( children , animation , duration , directional ) {
22
+ debug ( 'wrapChildren()' )
29
23
30
- static getDerivedStateFromProps ( props , state ) {
31
- debug ( 'getDerivedStateFromProps()' )
32
-
33
- const { animation, duration, directional } = props
34
- const { children : prevMapping } = state
35
-
36
- // A short circuit for an initial render as there will be no `prevMapping`
37
- if ( typeof prevMapping === 'undefined' ) {
38
- return {
39
- children : _ . mapValues ( getChildMapping ( props . children ) , ( child ) =>
40
- wrapChild ( child , state . handleOnHide , {
41
- animation,
42
- duration,
43
- directional,
44
- } ) ,
45
- ) ,
46
- }
47
- }
24
+ const [ , forceUpdate ] = React . useReducer ( ( x ) => x + 1 , 0 )
25
+
26
+ const previousChildren = React . useRef ( )
27
+ let wrappedChildren
48
28
49
- const nextMapping = getChildMapping ( props . children )
50
- const children = mergeChildMappings ( prevMapping , nextMapping )
29
+ React . useEffect ( ( ) => {
30
+ previousChildren . current = wrappedChildren
31
+ } )
51
32
52
- _ . forEach ( children , ( child , key ) => {
53
- const hasPrev = _ . has ( prevMapping , key )
54
- const hasNext = _ . has ( nextMapping , key )
33
+ const handleChildHide = useEventCallback ( ( nothing , childProps ) => {
34
+ debug ( 'handleOnHide' , childProps )
35
+ const { reactKey } = childProps
36
+
37
+ delete previousChildren . current [ reactKey ]
38
+ forceUpdate ( )
39
+ } )
40
+
41
+ // A short circuit for an initial render as there will be no `prevMapping`
42
+ if ( typeof previousChildren . current === 'undefined' ) {
43
+ wrappedChildren = _ . mapValues ( getChildMapping ( children ) , ( child ) =>
44
+ wrapChild ( child , handleChildHide , {
45
+ animation,
46
+ duration,
47
+ directional,
48
+ } ) ,
49
+ )
50
+ } else {
51
+ const nextMapping = getChildMapping ( children )
52
+ wrappedChildren = mergeChildMappings ( previousChildren . current , nextMapping )
55
53
56
- const { [ key ] : prevChild } = prevMapping
54
+ _ . forEach ( wrappedChildren , ( child , key ) => {
55
+ const hasPrev = previousChildren . current [ key ]
56
+ const hasNext = nextMapping [ key ]
57
+
58
+ const prevChild = previousChildren . current [ key ]
57
59
const isLeaving = ! _ . get ( prevChild , 'props.visible' )
58
60
59
61
// Heads up!
60
62
// An item is new (entering), it will be picked from `nextChildren`, so it should be wrapped
61
63
if ( hasNext && ( ! hasPrev || isLeaving ) ) {
62
- children [ key ] = wrapChild ( child , state . handleOnHide , {
64
+ wrappedChildren [ key ] = wrapChild ( child , handleChildHide , {
63
65
animation,
64
66
duration,
65
67
directional,
@@ -72,7 +74,7 @@ export default class TransitionGroup extends React.Component {
72
74
// An item is old (exiting), it will be picked from `prevChildren`, so it has been already
73
75
// wrapped, so should be only updated
74
76
if ( ! hasNext && hasPrev && ! isLeaving ) {
75
- children [ key ] = React . cloneElement ( prevChild , { visible : false } )
77
+ wrappedChildren [ key ] = React . cloneElement ( prevChild , { visible : false } )
76
78
return
77
79
}
78
80
@@ -83,31 +85,44 @@ export default class TransitionGroup extends React.Component {
83
85
props : { visible, transitionOnMount } ,
84
86
} = prevChild
85
87
86
- children [ key ] = wrapChild ( child , state . handleOnHide , {
88
+ wrappedChildren [ key ] = wrapChild ( child , handleChildHide , {
87
89
animation,
88
90
duration,
89
91
directional,
90
92
transitionOnMount,
91
93
visible,
92
94
} )
93
95
} )
94
-
95
- return { children }
96
96
}
97
97
98
- render ( ) {
99
- debug ( 'render' )
100
- debug ( 'props' , this . props )
101
- debug ( 'state' , this . state )
102
-
103
- const { children } = this . state
104
- const ElementType = getElementType ( TransitionGroup , this . props )
105
- const rest = getUnhandledProps ( TransitionGroup , this . props )
106
-
107
- return < ElementType { ...rest } > { _ . values ( children ) } </ ElementType >
108
- }
98
+ return wrappedChildren
109
99
}
110
100
101
+ /**
102
+ * A Transition.Group animates children as they mount and unmount.
103
+ */
104
+ const TransitionGroup = React . forwardRef ( function ( props , ref ) {
105
+ debug ( 'render' )
106
+ debug ( 'props' , props )
107
+
108
+ const children = useWrappedChildren (
109
+ props . children ,
110
+ props . animation ,
111
+ props . duration ,
112
+ props . directional ,
113
+ )
114
+
115
+ const ElementType = getElementType ( TransitionGroup , props )
116
+ const rest = getUnhandledProps ( TransitionGroup , props )
117
+
118
+ return (
119
+ < ElementType { ...rest } ref = { ref } >
120
+ { _ . values ( children ) }
121
+ </ ElementType >
122
+ )
123
+ } )
124
+
125
+ TransitionGroup . displayName = 'TransitionGroup'
111
126
TransitionGroup . propTypes = {
112
127
/** An element type to render as (string or function). */
113
128
as : PropTypes . elementType ,
@@ -137,3 +152,5 @@ TransitionGroup.defaultProps = {
137
152
animation : 'fade' ,
138
153
duration : 500 ,
139
154
}
155
+
156
+ export default TransitionGroup
0 commit comments