@@ -4,14 +4,18 @@ import { useLayoutEffect } from 'react-layout-effect'
4
4
import {
5
5
is ,
6
6
each ,
7
+ raf ,
7
8
useForceUpdate ,
8
- FluidConfig ,
9
+ useMemoOne ,
9
10
useOnce ,
10
- usePrev ,
11
+ FluidEvent ,
12
+ FluidObserver ,
13
+ FluidValue ,
11
14
} from '@react-spring/shared'
12
15
import { ElementType } from '@react-spring/types'
13
16
14
- import { AnimatedProps } from './AnimatedProps'
17
+ import { AnimatedObject } from './AnimatedObject'
18
+ import { TreeContext } from './context'
15
19
import { HostConfig } from './createHost'
16
20
17
21
export type AnimatableComponent = string | Exclude < ElementType , string >
@@ -37,49 +41,82 @@ export const withAnimated = (Component: any, host: HostConfig) => {
37
41
[ givenRef ]
38
42
)
39
43
44
+ const [ props , deps ] = getAnimatedState ( givenProps , host )
45
+
40
46
const forceUpdate = useForceUpdate ( )
41
- const props = new AnimatedProps ( ( ) => {
47
+ const observer = new PropsObserver ( ( ) => {
42
48
const instance = instanceRef . current
43
49
if ( hasInstance && ! instance ) {
44
- return // The wrapped component forgot to forward its ref.
50
+ // Either this component was unmounted before changes could be
51
+ // applied, or the wrapped component forgot to forward its ref.
52
+ return
45
53
}
46
54
47
55
const didUpdate = instance
48
- ? host . applyAnimatedValues ( instance , props . getValue ( true ) ! )
56
+ ? host . applyAnimatedValues ( instance , props . getValue ( true ) )
49
57
: false
50
58
51
59
// Re-render the component when native updates fail.
52
60
if ( didUpdate === false ) {
53
61
forceUpdate ( )
54
62
}
55
- } )
63
+ } , deps )
56
64
57
- const dependencies = new Set < FluidConfig > ( )
58
- props . setValue ( givenProps , { dependencies, host } )
65
+ const observerRef = useRef < PropsObserver > ( )
66
+ useLayoutEffect ( ( ) => {
67
+ const lastObserver = observerRef . current
68
+ observerRef . current = observer
59
69
60
- const state = [ props , dependencies ] as const
61
- const stateRef = useRef ( state )
62
- const prevState = usePrev ( state )
70
+ // Observe the latest dependencies.
71
+ each ( deps , dep => dep . addChild ( observer ) )
63
72
64
- useLayoutEffect ( ( ) => {
65
- stateRef . current = state
66
- // Attach the new props to our latest dependencies.
67
- each ( dependencies , dep => dep . addChild ( props ) )
68
- // Detach the old props from our previous dependencies.
69
- if ( prevState ) each ( prevState [ 1 ] , dep => dep . removeChild ( prevState [ 0 ] ) )
73
+ // Stop observing previous dependencies.
74
+ if ( lastObserver ) {
75
+ each ( lastObserver . deps , dep => dep . removeChild ( lastObserver ) )
76
+ raf . cancel ( lastObserver . update )
77
+ }
70
78
} )
71
79
72
80
// Stop observing on unmount.
73
81
useOnce ( ( ) => ( ) => {
74
- const [ props , dependencies ] = stateRef . current
75
- each ( dependencies , dep => dep . removeChild ( props ) )
82
+ const observer = observerRef . current !
83
+ each ( observer . deps , dep => dep . removeChild ( observer ) )
76
84
} )
77
85
78
- const usedProps = host . getComponentProps ( props . getValue ( ) ! )
86
+ const usedProps = host . getComponentProps ( props . getValue ( ) )
79
87
return < Component { ...usedProps } ref = { ref } />
80
88
} )
81
89
}
82
90
91
+ class PropsObserver implements FluidObserver {
92
+ constructor ( readonly update : ( ) => void , readonly deps : Set < FluidValue > ) { }
93
+ onParentChange ( event : FluidEvent ) {
94
+ if ( event . type == 'change' ) {
95
+ raf . write ( this . update )
96
+ }
97
+ }
98
+ }
99
+
100
+ type AnimatedState = [ props : AnimatedObject , dependencies : Set < FluidValue > ]
101
+
102
+ function getAnimatedState ( props : any , host : HostConfig ) : AnimatedState {
103
+ const dependencies = new Set < FluidValue > ( )
104
+ TreeContext . dependencies = dependencies
105
+
106
+ // Search the style for dependencies.
107
+ if ( props . style )
108
+ props = {
109
+ ...props ,
110
+ style : host . createAnimatedStyle ( props . style ) ,
111
+ }
112
+
113
+ // Search the props for dependencies.
114
+ props = new AnimatedObject ( props )
115
+
116
+ TreeContext . dependencies = null
117
+ return [ props , dependencies ]
118
+ }
119
+
83
120
function updateRef < T > ( ref : Ref < T > , value : T ) {
84
121
if ( ref ) {
85
122
if ( is . fun ( ref ) ) ref ( value )
0 commit comments