5
5
* LICENSE file in the root directory of this source tree.
6
6
*/
7
7
8
- function componentWillMount ( ) {
9
- // Call this.constructor.gDSFP to support sub-classes.
10
- var state = this . constructor . getDerivedStateFromProps ( this . props , this . state ) ;
11
- if ( state !== null && state !== undefined ) {
12
- this . setState ( state ) ;
8
+ // Conceptually, getDerivedStateFromProps is like a setState updater function.
9
+ // But it needs to be called *after* all the other updates in the queue, and it
10
+ // must be called exactly once, right before shouldComponentUpdate.
11
+ //
12
+ // Other than getDerivedStateFormProps itself, there's no lifecycle that meets
13
+ // these requirements. componentWillReceiveProps fires only if the props have
14
+ // changed. componentWillUpdate fires too late.
15
+ //
16
+ // This polyfill works by monkeypatching instance.updater. updater is an
17
+ // internal-ish API that has stayed mostly stable across several major versions
18
+ // of React. The polyfill intercepts updates before they are added to the native
19
+ // update queue. Then it schedules a single update with the native update queue,
20
+ // in the form of an updater function. Inside that updater function, the
21
+ // intercepted updates are processed. Finally, getDerivedStateFromProps is
22
+ // called and applied. This approach guarantees that getDerivedStateFromProps is
23
+ // always called at the end.
24
+ function processUpdateQueue ( prevState , nextProps ) {
25
+ var queue = this . __updateQueue ;
26
+ this . __updateQueue = null ;
27
+ if ( queue === null || queue === undefined ) {
28
+ return null ;
13
29
}
30
+ // First process all the user-provided updates
31
+ var nextState = prevState ;
32
+ var update = null ;
33
+ var payload = null ;
34
+ var callback = null ;
35
+ for ( let i = 0 ; i < queue . length ; i ++ ) {
36
+ update = queue [ i ] ;
37
+ payload = update . payload ;
38
+ callback = update . callback ;
39
+ if ( typeof payload === 'function' ) {
40
+ payload = payload . call ( this , prevState , nextProps ) ;
41
+ }
42
+ if ( payload !== null && payload !== undefined ) {
43
+ nextState = Object . assign ( { } , prevState , payload ) ;
44
+ }
45
+ if ( callback !== null ) {
46
+ let callbacks = this . __callbacks ;
47
+ if ( callbacks === null || callbacks === undefined ) {
48
+ this . __callbacks = [ callback ] ;
49
+ } else {
50
+ callbacks . push ( callback ) ;
51
+ }
52
+ }
53
+ }
54
+ // Now that we've processed all the updates, we can call
55
+ // `getDerivedStateFromProps`. This always comes last.
56
+ var derivedState =
57
+ this . constructor . getDerivedStateFromProps ( nextProps , nextState ) ;
58
+ if ( derivedState !== null && derivedState !== undefined ) {
59
+ nextState = Object . assign ( { } , prevState , derivedState ) ;
60
+ }
61
+ return nextState ;
14
62
}
15
63
16
- function componentWillReceiveProps ( nextProps ) {
17
- // Call this.constructor.gDSFP to support sub-classes.
18
- // Use the setState() updater to ensure state isn't stale in certain edge cases.
19
- function updater ( prevState ) {
20
- var state = this . constructor . getDerivedStateFromProps ( nextProps , prevState ) ;
21
- return state !== null && state !== undefined ? state : null ;
22
- }
23
- // Binding "this" is important for shallow renderer support.
24
- this . setState ( updater . bind ( this ) ) ;
64
+ function componentWillMount ( ) {
65
+ const originalUpdater = this . updater ;
66
+
67
+ this . updater = {
68
+ enqueueSetState ( instance , payload , callback ) {
69
+ var update = {
70
+ payload : payload ,
71
+ callback : callback === undefined ? null : callback
72
+ } ;
73
+ let queue = instance . __updateQueue ;
74
+ if ( queue === null || queue === undefined ) {
75
+ // Create a queue of updates. This will act as a polyfill for the native
76
+ // update queue.
77
+ queue = instance . __updateQueue = [ update ] ;
78
+ // The native update queue should contain a single update function.
79
+ // In that function, we will process all the updates in the polyfilled
80
+ // queue. This allows us to call `getDerivedStateFromProps` after all
81
+ // the other updates have been processed.
82
+ originalUpdater . enqueueSetState ( instance , processUpdateQueue ) ;
83
+ } else {
84
+ // We already scheduled an update on the native queue. Push onto the
85
+ // polyfilled queue.
86
+ queue . push ( update ) ;
87
+ }
88
+ } ,
89
+ enqueueReplaceState ( ) {
90
+ // Not worth implementing this since class components do no have a
91
+ // `replaceState` method.
92
+ throw new Error (
93
+ 'react-lifecycles-compat: enqueueReplaceState is not supported'
94
+ ) ;
95
+ } ,
96
+ // Note: Because forceUpdate does not accept an updater function, we can't
97
+ // polyfill this correctly unless we're inside batchedUpdates. So gDSFP
98
+ // will not fire unless we receive new props OR there's another update in
99
+ // the same batch.
100
+ enqueueForceUpdate : originalUpdater . enqueueForceUpdate ,
101
+ enqueueCallback ( instance , callback ) {
102
+ instance . updater . enqueueSetState ( instance , null , callback ) ;
103
+ }
104
+ } ;
105
+
106
+ // Add an empty update to the queue to trigger getDerivedStateFromProps
107
+ this . setState ( null ) ;
108
+ }
109
+
110
+ function componentWillReceiveProps ( ) {
111
+ // Add an empty update to the queue to trigger getDerivedStateFromProps.
112
+ this . setState ( null ) ;
25
113
}
26
114
27
115
function componentWillUpdate ( nextProps , nextState ) {
@@ -109,33 +197,21 @@ export function polyfill(Component) {
109
197
) ;
110
198
}
111
199
112
- // React <= 16.2 does not support static getDerivedStateFromProps.
113
- // As a workaround, use cWM and cWRP to invoke the new static lifecycle.
114
- // Newer versions of React will ignore these lifecycles if gDSFP exists.
115
- if ( typeof Component . getDerivedStateFromProps === 'function' ) {
116
- prototype . componentWillMount = componentWillMount ;
117
- prototype . componentWillReceiveProps = componentWillReceiveProps ;
118
- }
119
-
120
- // React <= 16.2 does not support getSnapshotBeforeUpdate.
121
- // As a workaround, use cWU to invoke the new lifecycle.
122
- // Newer versions of React will ignore that lifecycle if gSBU exists.
123
- if ( typeof prototype . getSnapshotBeforeUpdate === 'function' ) {
124
- if ( typeof prototype . componentDidUpdate !== 'function' ) {
125
- throw new Error (
126
- 'Cannot polyfill getSnapshotBeforeUpdate() for components that do not define componentDidUpdate() on the prototype'
127
- ) ;
200
+ var componentDidUpdate = prototype . componentDidUpdate ;
201
+ function componentDidUpdatePolyfill ( prevProps , prevState , maybeSnapshot ) {
202
+ if ( typeof Component . getDerivedStateFromProps === 'function' ) {
203
+ // If getDerivedStateFromProps is defined, we polyfilled the update queue.
204
+ // Flush the callbacks here.
205
+ var callbacks = this . __callbacks ;
206
+ if ( callbacks !== null && callbacks !== undefined ) {
207
+ this . __callbacks = null ;
208
+ for ( var i = 0 ; i < callbacks . length ; i ++ ) {
209
+ callbacks [ i ] . call ( this ) ;
210
+ }
211
+ }
128
212
}
129
213
130
- prototype . componentWillUpdate = componentWillUpdate ;
131
-
132
- var componentDidUpdate = prototype . componentDidUpdate ;
133
-
134
- prototype . componentDidUpdate = function componentDidUpdatePolyfill (
135
- prevProps ,
136
- prevState ,
137
- maybeSnapshot
138
- ) {
214
+ if ( typeof componentDidUpdate === 'function' ) {
139
215
// 16.3+ will not execute our will-update method;
140
216
// It will pass a snapshot value to did-update though.
141
217
// Older versions will require our polyfilled will-update value.
@@ -149,7 +225,30 @@ export function polyfill(Component) {
149
225
: maybeSnapshot ;
150
226
151
227
componentDidUpdate . call ( this , prevProps , prevState , snapshot ) ;
152
- } ;
228
+ }
229
+ }
230
+
231
+ // React <= 16.2 does not support static getDerivedStateFromProps.
232
+ // As a workaround, use cWM and cWRP to invoke the new static lifecycle.
233
+ // Newer versions of React will ignore these lifecycles if gDSFP exists.
234
+ if ( typeof Component . getDerivedStateFromProps === 'function' ) {
235
+ prototype . componentWillMount = componentWillMount ;
236
+ prototype . componentWillReceiveProps = componentWillReceiveProps ;
237
+ prototype . componentDidUpdate = componentDidUpdatePolyfill ;
238
+ }
239
+
240
+ // React <= 16.2 does not support getSnapshotBeforeUpdate.
241
+ // As a workaround, use cWU to invoke the new lifecycle.
242
+ // Newer versions of React will ignore that lifecycle if gSBU exists.
243
+ if ( typeof prototype . getSnapshotBeforeUpdate === 'function' ) {
244
+ prototype . componentWillUpdate = componentWillUpdate ;
245
+ if ( typeof prototype . componentDidUpdate !== 'function' ) {
246
+ throw new Error (
247
+ 'Cannot polyfill getSnapshotBeforeUpdate() for components that do ' +
248
+ 'not define componentDidUpdate() on the prototype'
249
+ ) ;
250
+ }
251
+ prototype . componentDidUpdate = componentDidUpdatePolyfill ;
153
252
}
154
253
155
254
return Component ;
0 commit comments