Add forked copies of reconciler modules
All changes in this commit were generated by the following commands. Copy each module that ends with `.old` to a new file that ends with `.new`: ```sh for f in packages/react-reconciler/src/*.old.js; do cp "$f" "$(echo "$f" | sed s/\.old/\.new/)"; done ``` Then transform the internal imports: ```sh grep -rl --include="*.new.js" '.old' packages/react-reconciler/src/| xargs sed -i '' "s/\.old\'/\.new\'/g" ```
Andrew Clark committed
Apr 8, 2020 at 23:11 UTC
17f582e0453b808860be59ed3437c6a426ae52de
30 files changed
+22052
packages/react-reconciler/src/ReactChildFiber.new.js
new
+1490
@@ -0,0 +1,1490 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {ReactElement} from 'shared/ReactElementType';
11
+import type {ReactPortal} from 'shared/ReactTypes';
12
+import type {BlockComponent} from 'react/src/ReactBlock';
13
+import type {LazyComponent} from 'react/src/ReactLazy';
14
+import type {Fiber} from './ReactInternalTypes';
15
+import type {ExpirationTime} from './ReactFiberExpirationTime';
16
+
17
+import getComponentName from 'shared/getComponentName';
18
+import {Placement, Deletion} from './ReactSideEffectTags';
19
+import {
20
+ getIteratorFn,
21
+ REACT_ELEMENT_TYPE,
22
+ REACT_FRAGMENT_TYPE,
23
+ REACT_PORTAL_TYPE,
24
+ REACT_LAZY_TYPE,
25
+ REACT_BLOCK_TYPE,
26
+} from 'shared/ReactSymbols';
27
+import {
28
+ FunctionComponent,
29
+ ClassComponent,
30
+ HostText,
31
+ HostPortal,
32
+ Fragment,
33
+ Block,
34
+} from './ReactWorkTags';
35
+import invariant from 'shared/invariant';
36
+import {warnAboutStringRefs, enableBlocksAPI} from 'shared/ReactFeatureFlags';
37
+
38
+import {
39
+ createWorkInProgress,
40
+ resetWorkInProgress,
41
+ createFiberFromElement,
42
+ createFiberFromFragment,
43
+ createFiberFromText,
44
+ createFiberFromPortal,
45
+} from './ReactFiber.new';
46
+import {emptyRefsObject} from './ReactFiberClassComponent.new';
47
+import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
48
+import {getCurrentFiberStackInDev} from './ReactCurrentFiber';
49
+import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading.new';
50
+import {StrictMode} from './ReactTypeOfMode';
51
+
52
+let didWarnAboutMaps;
53
+let didWarnAboutGenerators;
54
+let didWarnAboutStringRefs;
55
+let ownerHasKeyUseWarning;
56
+let ownerHasFunctionTypeWarning;
57
+let warnForMissingKey = (child: mixed) => {};
58
+
59
+if (__DEV__) {
60
+ didWarnAboutMaps = false;
61
+ didWarnAboutGenerators = false;
62
+ didWarnAboutStringRefs = {};
63
+
64
+ /**
65
+ * Warn if there's no key explicitly set on dynamic arrays of children or
66
+ * object keys are not valid. This allows us to keep track of children between
67
+ * updates.
68
+ */
69
+ ownerHasKeyUseWarning = {};
70
+ ownerHasFunctionTypeWarning = {};
71
+
72
+ warnForMissingKey = (child: mixed) => {
73
+ if (child === null || typeof child !== 'object') {
74
+ return;
75
+ }
76
+ if (!child._store || child._store.validated || child.key != null) {
77
+ return;
78
+ }
79
+ invariant(
80
+ typeof child._store === 'object',
81
+ 'React Component in warnForMissingKey should have a _store. ' +
82
+ 'This error is likely caused by a bug in React. Please file an issue.',
83
+ );
84
+ child._store.validated = true;
85
+
86
+ const currentComponentErrorInfo =
87
+ 'Each child in a list should have a unique ' +
88
+ '"key" prop. See https://fb.me/react-warning-keys for ' +
89
+ 'more information.' +
90
+ getCurrentFiberStackInDev();
91
+ if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
92
+ return;
93
+ }
94
+ ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
95
+
96
+ console.error(
97
+ 'Each child in a list should have a unique ' +
98
+ '"key" prop. See https://fb.me/react-warning-keys for ' +
99
+ 'more information.',
100
+ );
101
+ };
102
+}
103
+
104
+const isArray = Array.isArray;
105
+
106
+function coerceRef(
107
+ returnFiber: Fiber,
108
+ current: Fiber | null,
109
+ element: ReactElement,
110
+) {
111
+ const mixedRef = element.ref;
112
+ if (
113
+ mixedRef !== null &&
114
+ typeof mixedRef !== 'function' &&
115
+ typeof mixedRef !== 'object'
116
+ ) {
117
+ if (__DEV__) {
118
+ // TODO: Clean this up once we turn on the string ref warning for
119
+ // everyone, because the strict mode case will no longer be relevant
120
+ if (
121
+ (returnFiber.mode & StrictMode || warnAboutStringRefs) &&
122
+ // We warn in ReactElement.js if owner and self are equal for string refs
123
+ // because these cannot be automatically converted to an arrow function
124
+ // using a codemod. Therefore, we don't have to warn about string refs again.
125
+ !(
126
+ element._owner &&
127
+ element._self &&
128
+ element._owner.stateNode !== element._self
129
+ )
130
+ ) {
131
+ const componentName = getComponentName(returnFiber.type) || 'Component';
132
+ if (!didWarnAboutStringRefs[componentName]) {
133
+ if (warnAboutStringRefs) {
134
+ console.error(
135
+ 'Component "%s" contains the string ref "%s". Support for string refs ' +
136
+ 'will be removed in a future major release. We recommend using ' +
137
+ 'useRef() or createRef() instead. ' +
138
+ 'Learn more about using refs safely here: ' +
139
+ 'https://fb.me/react-strict-mode-string-ref%s',
140
+ componentName,
141
+ mixedRef,
142
+ getStackByFiberInDevAndProd(returnFiber),
143
+ );
144
+ } else {
145
+ console.error(
146
+ 'A string ref, "%s", has been found within a strict mode tree. ' +
147
+ 'String refs are a source of potential bugs and should be avoided. ' +
148
+ 'We recommend using useRef() or createRef() instead. ' +
149
+ 'Learn more about using refs safely here: ' +
150
+ 'https://fb.me/react-strict-mode-string-ref%s',
151
+ mixedRef,
152
+ getStackByFiberInDevAndProd(returnFiber),
153
+ );
154
+ }
155
+ didWarnAboutStringRefs[componentName] = true;
156
+ }
157
+ }
158
+ }
159
+
160
+ if (element._owner) {
161
+ const owner: ?Fiber = (element._owner: any);
162
+ let inst;
163
+ if (owner) {
164
+ const ownerFiber = ((owner: any): Fiber);
165
+ invariant(
166
+ ownerFiber.tag === ClassComponent,
167
+ 'Function components cannot have string refs. ' +
168
+ 'We recommend using useRef() instead. ' +
169
+ 'Learn more about using refs safely here: ' +
170
+ 'https://fb.me/react-strict-mode-string-ref',
171
+ );
172
+ inst = ownerFiber.stateNode;
173
+ }
174
+ invariant(
175
+ inst,
176
+ 'Missing owner for string ref %s. This error is likely caused by a ' +
177
+ 'bug in React. Please file an issue.',
178
+ mixedRef,
179
+ );
180
+ const stringRef = '' + mixedRef;
181
+ // Check if previous string ref matches new string ref
182
+ if (
183
+ current !== null &&
184
+ current.ref !== null &&
185
+ typeof current.ref === 'function' &&
186
+ current.ref._stringRef === stringRef
187
+ ) {
188
+ return current.ref;
189
+ }
190
+ const ref = function(value) {
191
+ let refs = inst.refs;
192
+ if (refs === emptyRefsObject) {
193
+ // This is a lazy pooled frozen object, so we need to initialize.
194
+ refs = inst.refs = {};
195
+ }
196
+ if (value === null) {
197
+ delete refs[stringRef];
198
+ } else {
199
+ refs[stringRef] = value;
200
+ }
201
+ };
202
+ ref._stringRef = stringRef;
203
+ return ref;
204
+ } else {
205
+ invariant(
206
+ typeof mixedRef === 'string',
207
+ 'Expected ref to be a function, a string, an object returned by React.createRef(), or null.',
208
+ );
209
+ invariant(
210
+ element._owner,
211
+ 'Element ref was specified as a string (%s) but no owner was set. This could happen for one of' +
212
+ ' the following reasons:\n' +
213
+ '1. You may be adding a ref to a function component\n' +
214
+ "2. You may be adding a ref to a component that was not created inside a component's render method\n" +
215
+ '3. You have multiple copies of React loaded\n' +
216
+ 'See https://fb.me/react-refs-must-have-owner for more information.',
217
+ mixedRef,
218
+ );
219
+ }
220
+ }
221
+ return mixedRef;
222
+}
223
+
224
+function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {
225
+ if (returnFiber.type !== 'textarea') {
226
+ let addendum = '';
227
+ if (__DEV__) {
228
+ addendum =
229
+ ' If you meant to render a collection of children, use an array ' +
230
+ 'instead.' +
231
+ getCurrentFiberStackInDev();
232
+ }
233
+ invariant(
234
+ false,
235
+ 'Objects are not valid as a React child (found: %s).%s',
236
+ Object.prototype.toString.call(newChild) === '[object Object]'
237
+ ? 'object with keys {' + Object.keys(newChild).join(', ') + '}'
238
+ : newChild,
239
+ addendum,
240
+ );
241
+ }
242
+}
243
+
244
+function warnOnFunctionType() {
245
+ if (__DEV__) {
246
+ const currentComponentErrorInfo =
247
+ 'Functions are not valid as a React child. This may happen if ' +
248
+ 'you return a Component instead of <Component /> from render. ' +
249
+ 'Or maybe you meant to call this function rather than return it.' +
250
+ getCurrentFiberStackInDev();
251
+
252
+ if (ownerHasFunctionTypeWarning[currentComponentErrorInfo]) {
253
+ return;
254
+ }
255
+ ownerHasFunctionTypeWarning[currentComponentErrorInfo] = true;
256
+
257
+ console.error(
258
+ 'Functions are not valid as a React child. This may happen if ' +
259
+ 'you return a Component instead of <Component /> from render. ' +
260
+ 'Or maybe you meant to call this function rather than return it.',
261
+ );
262
+ }
263
+}
264
+
265
+// We avoid inlining this to avoid potential deopts from using try/catch.
266
+/** @noinline */
267
+function resolveLazyType<T, P>(
268
+ lazyComponent: LazyComponent<T, P>,
269
+): LazyComponent<T, P> | T {
270
+ try {
271
+ // If we can, let's peek at the resulting type.
272
+ const payload = lazyComponent._payload;
273
+ const init = lazyComponent._init;
274
+ return init(payload);
275
+ } catch (x) {
276
+ // Leave it in place and let it throw again in the begin phase.
277
+ return lazyComponent;
278
+ }
279
+}
280
+
281
+// This wrapper function exists because I expect to clone the code in each path
282
+// to be able to optimize each path individually by branching early. This needs
283
+// a compiler or we can do it manually. Helpers that don't need this branching
284
+// live outside of this function.
285
+function ChildReconciler(shouldTrackSideEffects) {
286
+ function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
287
+ if (!shouldTrackSideEffects) {
288
+ // Noop.
289
+ return;
290
+ }
291
+ // Deletions are added in reversed order so we add it to the front.
292
+ // At this point, the return fiber's effect list is empty except for
293
+ // deletions, so we can just append the deletion to the list. The remaining
294
+ // effects aren't added until the complete phase. Once we implement
295
+ // resuming, this may not be true.
296
+ const last = returnFiber.lastEffect;
297
+ if (last !== null) {
298
+ last.nextEffect = childToDelete;
299
+ returnFiber.lastEffect = childToDelete;
300
+ } else {
301
+ returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
302
+ }
303
+ childToDelete.nextEffect = null;
304
+ childToDelete.effectTag = Deletion;
305
+ }
306
+
307
+ function deleteRemainingChildren(
308
+ returnFiber: Fiber,
309
+ currentFirstChild: Fiber | null,
310
+ ): null {
311
+ if (!shouldTrackSideEffects) {
312
+ // Noop.
313
+ return null;
314
+ }
315
+
316
+ // TODO: For the shouldClone case, this could be micro-optimized a bit by
317
+ // assuming that after the first child we've already added everything.
318
+ let childToDelete = currentFirstChild;
319
+ while (childToDelete !== null) {
320
+ deleteChild(returnFiber, childToDelete);
321
+ childToDelete = childToDelete.sibling;
322
+ }
323
+ return null;
324
+ }
325
+
326
+ function mapRemainingChildren(
327
+ returnFiber: Fiber,
328
+ currentFirstChild: Fiber,
329
+ ): Map<string | number, Fiber> {
330
+ // Add the remaining children to a temporary map so that we can find them by
331
+ // keys quickly. Implicit (null) keys get added to this set with their index
332
+ // instead.
333
+ const existingChildren: Map<string | number, Fiber> = new Map();
334
+
335
+ let existingChild = currentFirstChild;
336
+ while (existingChild !== null) {
337
+ if (existingChild.key !== null) {
338
+ existingChildren.set(existingChild.key, existingChild);
339
+ } else {
340
+ existingChildren.set(existingChild.index, existingChild);
341
+ }
342
+ existingChild = existingChild.sibling;
343
+ }
344
+ return existingChildren;
345
+ }
346
+
347
+ function useFiber(fiber: Fiber, pendingProps: mixed): Fiber {
348
+ // We currently set sibling to null and index to 0 here because it is easy
349
+ // to forget to do before returning it. E.g. for the single child case.
350
+ const clone = createWorkInProgress(fiber, pendingProps);
351
+ clone.index = 0;
352
+ clone.sibling = null;
353
+ return clone;
354
+ }
355
+
356
+ function placeChild(
357
+ newFiber: Fiber,
358
+ lastPlacedIndex: number,
359
+ newIndex: number,
360
+ ): number {
361
+ newFiber.index = newIndex;
362
+ if (!shouldTrackSideEffects) {
363
+ // Noop.
364
+ return lastPlacedIndex;
365
+ }
366
+ const current = newFiber.alternate;
367
+ if (current !== null) {
368
+ const oldIndex = current.index;
369
+ if (oldIndex < lastPlacedIndex) {
370
+ // This is a move.
371
+ newFiber.effectTag = Placement;
372
+ return lastPlacedIndex;
373
+ } else {
374
+ // This item can stay in place.
375
+ return oldIndex;
376
+ }
377
+ } else {
378
+ // This is an insertion.
379
+ newFiber.effectTag = Placement;
380
+ return lastPlacedIndex;
381
+ }
382
+ }
383
+
384
+ function placeSingleChild(newFiber: Fiber): Fiber {
385
+ // This is simpler for the single child case. We only need to do a
386
+ // placement for inserting new children.
387
+ if (shouldTrackSideEffects && newFiber.alternate === null) {
388
+ newFiber.effectTag = Placement;
389
+ }
390
+ return newFiber;
391
+ }
392
+
393
+ function updateTextNode(
394
+ returnFiber: Fiber,
395
+ current: Fiber | null,
396
+ textContent: string,
397
+ expirationTime: ExpirationTime,
398
+ ) {
399
+ if (current === null || current.tag !== HostText) {
400
+ // Insert
401
+ const created = createFiberFromText(
402
+ textContent,
403
+ returnFiber.mode,
404
+ expirationTime,
405
+ );
406
+ created.return = returnFiber;
407
+ return created;
408
+ } else {
409
+ // Update
410
+ const existing = useFiber(current, textContent);
411
+ existing.return = returnFiber;
412
+ return existing;
413
+ }
414
+ }
415
+
416
+ function updateElement(
417
+ returnFiber: Fiber,
418
+ current: Fiber | null,
419
+ element: ReactElement,
420
+ expirationTime: ExpirationTime,
421
+ ): Fiber {
422
+ if (current !== null) {
423
+ if (
424
+ current.elementType === element.type ||
425
+ // Keep this check inline so it only runs on the false path:
426
+ (__DEV__ ? isCompatibleFamilyForHotReloading(current, element) : false)
427
+ ) {
428
+ // Move based on index
429
+ const existing = useFiber(current, element.props);
430
+ existing.ref = coerceRef(returnFiber, current, element);
431
+ existing.return = returnFiber;
432
+ if (__DEV__) {
433
+ existing._debugSource = element._source;
434
+ existing._debugOwner = element._owner;
435
+ }
436
+ return existing;
437
+ } else if (enableBlocksAPI && current.tag === Block) {
438
+ // The new Block might not be initialized yet. We need to initialize
439
+ // it in case initializing it turns out it would match.
440
+ let type = element.type;
441
+ if (type.$$typeof === REACT_LAZY_TYPE) {
442
+ type = resolveLazyType(type);
443
+ }
444
+ if (
445
+ type.$$typeof === REACT_BLOCK_TYPE &&
446
+ ((type: any): BlockComponent<any, any>)._render ===
447
+ (current.type: BlockComponent<any, any>)._render
448
+ ) {
449
+ // Same as above but also update the .type field.
450
+ const existing = useFiber(current, element.props);
451
+ existing.return = returnFiber;
452
+ existing.type = type;
453
+ if (__DEV__) {
454
+ existing._debugSource = element._source;
455
+ existing._debugOwner = element._owner;
456
+ }
457
+ return existing;
458
+ }
459
+ }
460
+ }
461
+ // Insert
462
+ const created = createFiberFromElement(
463
+ element,
464
+ returnFiber.mode,
465
+ expirationTime,
466
+ );
467
+ created.ref = coerceRef(returnFiber, current, element);
468
+ created.return = returnFiber;
469
+ return created;
470
+ }
471
+
472
+ function updatePortal(
473
+ returnFiber: Fiber,
474
+ current: Fiber | null,
475
+ portal: ReactPortal,
476
+ expirationTime: ExpirationTime,
477
+ ): Fiber {
478
+ if (
479
+ current === null ||
480
+ current.tag !== HostPortal ||
481
+ current.stateNode.containerInfo !== portal.containerInfo ||
482
+ current.stateNode.implementation !== portal.implementation
483
+ ) {
484
+ // Insert
485
+ const created = createFiberFromPortal(
486
+ portal,
487
+ returnFiber.mode,
488
+ expirationTime,
489
+ );
490
+ created.return = returnFiber;
491
+ return created;
492
+ } else {
493
+ // Update
494
+ const existing = useFiber(current, portal.children || []);
495
+ existing.return = returnFiber;
496
+ return existing;
497
+ }
498
+ }
499
+
500
+ function updateFragment(
501
+ returnFiber: Fiber,
502
+ current: Fiber | null,
503
+ fragment: Iterable<*>,
504
+ expirationTime: ExpirationTime,
505
+ key: null | string,
506
+ ): Fiber {
507
+ if (current === null || current.tag !== Fragment) {
508
+ // Insert
509
+ const created = createFiberFromFragment(
510
+ fragment,
511
+ returnFiber.mode,
512
+ expirationTime,
513
+ key,
514
+ );
515
+ created.return = returnFiber;
516
+ return created;
517
+ } else {
518
+ // Update
519
+ const existing = useFiber(current, fragment);
520
+ existing.return = returnFiber;
521
+ return existing;
522
+ }
523
+ }
524
+
525
+ function createChild(
526
+ returnFiber: Fiber,
527
+ newChild: any,
528
+ expirationTime: ExpirationTime,
529
+ ): Fiber | null {
530
+ if (typeof newChild === 'string' || typeof newChild === 'number') {
531
+ // Text nodes don't have keys. If the previous node is implicitly keyed
532
+ // we can continue to replace it without aborting even if it is not a text
533
+ // node.
534
+ const created = createFiberFromText(
535
+ '' + newChild,
536
+ returnFiber.mode,
537
+ expirationTime,
538
+ );
539
+ created.return = returnFiber;
540
+ return created;
541
+ }
542
+
543
+ if (typeof newChild === 'object' && newChild !== null) {
544
+ switch (newChild.$$typeof) {
545
+ case REACT_ELEMENT_TYPE: {
546
+ const created = createFiberFromElement(
547
+ newChild,
548
+ returnFiber.mode,
549
+ expirationTime,
550
+ );
551
+ created.ref = coerceRef(returnFiber, null, newChild);
552
+ created.return = returnFiber;
553
+ return created;
554
+ }
555
+ case REACT_PORTAL_TYPE: {
556
+ const created = createFiberFromPortal(
557
+ newChild,
558
+ returnFiber.mode,
559
+ expirationTime,
560
+ );
561
+ created.return = returnFiber;
562
+ return created;
563
+ }
564
+ }
565
+
566
+ if (isArray(newChild) || getIteratorFn(newChild)) {
567
+ const created = createFiberFromFragment(
568
+ newChild,
569
+ returnFiber.mode,
570
+ expirationTime,
571
+ null,
572
+ );
573
+ created.return = returnFiber;
574
+ return created;
575
+ }
576
+
577
+ throwOnInvalidObjectType(returnFiber, newChild);
578
+ }
579
+
580
+ if (__DEV__) {
581
+ if (typeof newChild === 'function') {
582
+ warnOnFunctionType();
583
+ }
584
+ }
585
+
586
+ return null;
587
+ }
588
+
589
+ function updateSlot(
590
+ returnFiber: Fiber,
591
+ oldFiber: Fiber | null,
592
+ newChild: any,
593
+ expirationTime: ExpirationTime,
594
+ ): Fiber | null {
595
+ // Update the fiber if the keys match, otherwise return null.
596
+
597
+ const key = oldFiber !== null ? oldFiber.key : null;
598
+
599
+ if (typeof newChild === 'string' || typeof newChild === 'number') {
600
+ // Text nodes don't have keys. If the previous node is implicitly keyed
601
+ // we can continue to replace it without aborting even if it is not a text
602
+ // node.
603
+ if (key !== null) {
604
+ return null;
605
+ }
606
+ return updateTextNode(
607
+ returnFiber,
608
+ oldFiber,
609
+ '' + newChild,
610
+ expirationTime,
611
+ );
612
+ }
613
+
614
+ if (typeof newChild === 'object' && newChild !== null) {
615
+ switch (newChild.$$typeof) {
616
+ case REACT_ELEMENT_TYPE: {
617
+ if (newChild.key === key) {
618
+ if (newChild.type === REACT_FRAGMENT_TYPE) {
619
+ return updateFragment(
620
+ returnFiber,
621
+ oldFiber,
622
+ newChild.props.children,
623
+ expirationTime,
624
+ key,
625
+ );
626
+ }
627
+ return updateElement(
628
+ returnFiber,
629
+ oldFiber,
630
+ newChild,
631
+ expirationTime,
632
+ );
633
+ } else {
634
+ return null;
635
+ }
636
+ }
637
+ case REACT_PORTAL_TYPE: {
638
+ if (newChild.key === key) {
639
+ return updatePortal(
640
+ returnFiber,
641
+ oldFiber,
642
+ newChild,
643
+ expirationTime,
644
+ );
645
+ } else {
646
+ return null;
647
+ }
648
+ }
649
+ }
650
+
651
+ if (isArray(newChild) || getIteratorFn(newChild)) {
652
+ if (key !== null) {
653
+ return null;
654
+ }
655
+
656
+ return updateFragment(
657
+ returnFiber,
658
+ oldFiber,
659
+ newChild,
660
+ expirationTime,
661
+ null,
662
+ );
663
+ }
664
+
665
+ throwOnInvalidObjectType(returnFiber, newChild);
666
+ }
667
+
668
+ if (__DEV__) {
669
+ if (typeof newChild === 'function') {
670
+ warnOnFunctionType();
671
+ }
672
+ }
673
+
674
+ return null;
675
+ }
676
+
677
+ function updateFromMap(
678
+ existingChildren: Map<string | number, Fiber>,
679
+ returnFiber: Fiber,
680
+ newIdx: number,
681
+ newChild: any,
682
+ expirationTime: ExpirationTime,
683
+ ): Fiber | null {
684
+ if (typeof newChild === 'string' || typeof newChild === 'number') {
685
+ // Text nodes don't have keys, so we neither have to check the old nor
686
+ // new node for the key. If both are text nodes, they match.
687
+ const matchedFiber = existingChildren.get(newIdx) || null;
688
+ return updateTextNode(
689
+ returnFiber,
690
+ matchedFiber,
691
+ '' + newChild,
692
+ expirationTime,
693
+ );
694
+ }
695
+
696
+ if (typeof newChild === 'object' && newChild !== null) {
697
+ switch (newChild.$$typeof) {
698
+ case REACT_ELEMENT_TYPE: {
699
+ const matchedFiber =
700
+ existingChildren.get(
701
+ newChild.key === null ? newIdx : newChild.key,
702
+ ) || null;
703
+ if (newChild.type === REACT_FRAGMENT_TYPE) {
704
+ return updateFragment(
705
+ returnFiber,
706
+ matchedFiber,
707
+ newChild.props.children,
708
+ expirationTime,
709
+ newChild.key,
710
+ );
711
+ }
712
+ return updateElement(
713
+ returnFiber,
714
+ matchedFiber,
715
+ newChild,
716
+ expirationTime,
717
+ );
718
+ }
719
+ case REACT_PORTAL_TYPE: {
720
+ const matchedFiber =
721
+ existingChildren.get(
722
+ newChild.key === null ? newIdx : newChild.key,
723
+ ) || null;
724
+ return updatePortal(
725
+ returnFiber,
726
+ matchedFiber,
727
+ newChild,
728
+ expirationTime,
729
+ );
730
+ }
731
+ }
732
+
733
+ if (isArray(newChild) || getIteratorFn(newChild)) {
734
+ const matchedFiber = existingChildren.get(newIdx) || null;
735
+ return updateFragment(
736
+ returnFiber,
737
+ matchedFiber,
738
+ newChild,
739
+ expirationTime,
740
+ null,
741
+ );
742
+ }
743
+
744
+ throwOnInvalidObjectType(returnFiber, newChild);
745
+ }
746
+
747
+ if (__DEV__) {
748
+ if (typeof newChild === 'function') {
749
+ warnOnFunctionType();
750
+ }
751
+ }
752
+
753
+ return null;
754
+ }
755
+
756
+ /**
757
+ * Warns if there is a duplicate or missing key
758
+ */
759
+ function warnOnInvalidKey(
760
+ child: mixed,
761
+ knownKeys: Set<string> | null,
762
+ ): Set<string> | null {
763
+ if (__DEV__) {
764
+ if (typeof child !== 'object' || child === null) {
765
+ return knownKeys;
766
+ }
767
+ switch (child.$$typeof) {
768
+ case REACT_ELEMENT_TYPE:
769
+ case REACT_PORTAL_TYPE:
770
+ warnForMissingKey(child);
771
+ const key = child.key;
772
+ if (typeof key !== 'string') {
773
+ break;
774
+ }
775
+ if (knownKeys === null) {
776
+ knownKeys = new Set();
777
+ knownKeys.add(key);
778
+ break;
779
+ }
780
+ if (!knownKeys.has(key)) {
781
+ knownKeys.add(key);
782
+ break;
783
+ }
784
+ console.error(
785
+ 'Encountered two children with the same key, `%s`. ' +
786
+ 'Keys should be unique so that components maintain their identity ' +
787
+ 'across updates. Non-unique keys may cause children to be ' +
788
+ 'duplicated and/or omitted — the behavior is unsupported and ' +
789
+ 'could change in a future version.',
790
+ key,
791
+ );
792
+ break;
793
+ default:
794
+ break;
795
+ }
796
+ }
797
+ return knownKeys;
798
+ }
799
+
800
+ function reconcileChildrenArray(
801
+ returnFiber: Fiber,
802
+ currentFirstChild: Fiber | null,
803
+ newChildren: Array<*>,
804
+ expirationTime: ExpirationTime,
805
+ ): Fiber | null {
806
+ // This algorithm can't optimize by searching from both ends since we
807
+ // don't have backpointers on fibers. I'm trying to see how far we can get
808
+ // with that model. If it ends up not being worth the tradeoffs, we can
809
+ // add it later.
810
+
811
+ // Even with a two ended optimization, we'd want to optimize for the case
812
+ // where there are few changes and brute force the comparison instead of
813
+ // going for the Map. It'd like to explore hitting that path first in
814
+ // forward-only mode and only go for the Map once we notice that we need
815
+ // lots of look ahead. This doesn't handle reversal as well as two ended
816
+ // search but that's unusual. Besides, for the two ended optimization to
817
+ // work on Iterables, we'd need to copy the whole set.
818
+
819
+ // In this first iteration, we'll just live with hitting the bad case
820
+ // (adding everything to a Map) in for every insert/move.
821
+
822
+ // If you change this code, also update reconcileChildrenIterator() which
823
+ // uses the same algorithm.
824
+
825
+ if (__DEV__) {
826
+ // First, validate keys.
827
+ let knownKeys = null;
828
+ for (let i = 0; i < newChildren.length; i++) {
829
+ const child = newChildren[i];
830
+ knownKeys = warnOnInvalidKey(child, knownKeys);
831
+ }
832
+ }
833
+
834
+ let resultingFirstChild: Fiber | null = null;
835
+ let previousNewFiber: Fiber | null = null;
836
+
837
+ let oldFiber = currentFirstChild;
838
+ let lastPlacedIndex = 0;
839
+ let newIdx = 0;
840
+ let nextOldFiber = null;
841
+ for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
842
+ if (oldFiber.index > newIdx) {
843
+ nextOldFiber = oldFiber;
844
+ oldFiber = null;
845
+ } else {
846
+ nextOldFiber = oldFiber.sibling;
847
+ }
848
+ const newFiber = updateSlot(
849
+ returnFiber,
850
+ oldFiber,
851
+ newChildren[newIdx],
852
+ expirationTime,
853
+ );
854
+ if (newFiber === null) {
855
+ // TODO: This breaks on empty slots like null children. That's
856
+ // unfortunate because it triggers the slow path all the time. We need
857
+ // a better way to communicate whether this was a miss or null,
858
+ // boolean, undefined, etc.
859
+ if (oldFiber === null) {
860
+ oldFiber = nextOldFiber;
861
+ }
862
+ break;
863
+ }
864
+ if (shouldTrackSideEffects) {
865
+ if (oldFiber && newFiber.alternate === null) {
866
+ // We matched the slot, but we didn't reuse the existing fiber, so we
867
+ // need to delete the existing child.
868
+ deleteChild(returnFiber, oldFiber);
869
+ }
870
+ }
871
+ lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
872
+ if (previousNewFiber === null) {
873
+ // TODO: Move out of the loop. This only happens for the first run.
874
+ resultingFirstChild = newFiber;
875
+ } else {
876
+ // TODO: Defer siblings if we're not at the right index for this slot.
877
+ // I.e. if we had null values before, then we want to defer this
878
+ // for each null value. However, we also don't want to call updateSlot
879
+ // with the previous one.
880
+ previousNewFiber.sibling = newFiber;
881
+ }
882
+ previousNewFiber = newFiber;
883
+ oldFiber = nextOldFiber;
884
+ }
885
+
886
+ if (newIdx === newChildren.length) {
887
+ // We've reached the end of the new children. We can delete the rest.
888
+ deleteRemainingChildren(returnFiber, oldFiber);
889
+ return resultingFirstChild;
890
+ }
891
+
892
+ if (oldFiber === null) {
893
+ // If we don't have any more existing children we can choose a fast path
894
+ // since the rest will all be insertions.
895
+ for (; newIdx < newChildren.length; newIdx++) {
896
+ const newFiber = createChild(
897
+ returnFiber,
898
+ newChildren[newIdx],
899
+ expirationTime,
900
+ );
901
+ if (newFiber === null) {
902
+ continue;
903
+ }
904
+ lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
905
+ if (previousNewFiber === null) {
906
+ // TODO: Move out of the loop. This only happens for the first run.
907
+ resultingFirstChild = newFiber;
908
+ } else {
909
+ previousNewFiber.sibling = newFiber;
910
+ }
911
+ previousNewFiber = newFiber;
912
+ }
913
+ return resultingFirstChild;
914
+ }
915
+
916
+ // Add all children to a key map for quick lookups.
917
+ const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
918
+
919
+ // Keep scanning and use the map to restore deleted items as moves.
920
+ for (; newIdx < newChildren.length; newIdx++) {
921
+ const newFiber = updateFromMap(
922
+ existingChildren,
923
+ returnFiber,
924
+ newIdx,
925
+ newChildren[newIdx],
926
+ expirationTime,
927
+ );
928
+ if (newFiber !== null) {
929
+ if (shouldTrackSideEffects) {
930
+ if (newFiber.alternate !== null) {
931
+ // The new fiber is a work in progress, but if there exists a
932
+ // current, that means that we reused the fiber. We need to delete
933
+ // it from the child list so that we don't add it to the deletion
934
+ // list.
935
+ existingChildren.delete(
936
+ newFiber.key === null ? newIdx : newFiber.key,
937
+ );
938
+ }
939
+ }
940
+ lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
941
+ if (previousNewFiber === null) {
942
+ resultingFirstChild = newFiber;
943
+ } else {
944
+ previousNewFiber.sibling = newFiber;
945
+ }
946
+ previousNewFiber = newFiber;
947
+ }
948
+ }
949
+
950
+ if (shouldTrackSideEffects) {
951
+ // Any existing children that weren't consumed above were deleted. We need
952
+ // to add them to the deletion list.
953
+ existingChildren.forEach(child => deleteChild(returnFiber, child));
954
+ }
955
+
956
+ return resultingFirstChild;
957
+ }
958
+
959
+ function reconcileChildrenIterator(
960
+ returnFiber: Fiber,
961
+ currentFirstChild: Fiber | null,
962
+ newChildrenIterable: Iterable<*>,
963
+ expirationTime: ExpirationTime,
964
+ ): Fiber | null {
965
+ // This is the same implementation as reconcileChildrenArray(),
966
+ // but using the iterator instead.
967
+
968
+ const iteratorFn = getIteratorFn(newChildrenIterable);
969
+ invariant(
970
+ typeof iteratorFn === 'function',
971
+ 'An object is not an iterable. This error is likely caused by a bug in ' +
972
+ 'React. Please file an issue.',
973
+ );
974
+
975
+ if (__DEV__) {
976
+ // We don't support rendering Generators because it's a mutation.
977
+ // See https://github.com/facebook/react/issues/12995
978
+ if (
979
+ typeof Symbol === 'function' &&
980
+ // $FlowFixMe Flow doesn't know about toStringTag
981
+ newChildrenIterable[Symbol.toStringTag] === 'Generator'
982
+ ) {
983
+ if (!didWarnAboutGenerators) {
984
+ console.error(
985
+ 'Using Generators as children is unsupported and will likely yield ' +
986
+ 'unexpected results because enumerating a generator mutates it. ' +
987
+ 'You may convert it to an array with `Array.from()` or the ' +
988
+ '`[...spread]` operator before rendering. Keep in mind ' +
989
+ 'you might need to polyfill these features for older browsers.',
990
+ );
991
+ }
992
+ didWarnAboutGenerators = true;
993
+ }
994
+
995
+ // Warn about using Maps as children
996
+ if ((newChildrenIterable: any).entries === iteratorFn) {
997
+ if (!didWarnAboutMaps) {
998
+ console.error(
999
+ 'Using Maps as children is not supported. ' +
1000
+ 'Use an array of keyed ReactElements instead.',
1001
+ );
1002
+ }
1003
+ didWarnAboutMaps = true;
1004
+ }
1005
+
1006
+ // First, validate keys.
1007
+ // We'll get a different iterator later for the main pass.
1008
+ const newChildren = iteratorFn.call(newChildrenIterable);
1009
+ if (newChildren) {
1010
+ let knownKeys = null;
1011
+ let step = newChildren.next();
1012
+ for (; !step.done; step = newChildren.next()) {
1013
+ const child = step.value;
1014
+ knownKeys = warnOnInvalidKey(child, knownKeys);
1015
+ }
1016
+ }
1017
+ }
1018
+
1019
+ const newChildren = iteratorFn.call(newChildrenIterable);
1020
+ invariant(newChildren != null, 'An iterable object provided no iterator.');
1021
+
1022
+ let resultingFirstChild: Fiber | null = null;
1023
+ let previousNewFiber: Fiber | null = null;
1024
+
1025
+ let oldFiber = currentFirstChild;
1026
+ let lastPlacedIndex = 0;
1027
+ let newIdx = 0;
1028
+ let nextOldFiber = null;
1029
+
1030
+ let step = newChildren.next();
1031
+ for (
1032
+ ;
1033
+ oldFiber !== null && !step.done;
1034
+ newIdx++, step = newChildren.next()
1035
+ ) {
1036
+ if (oldFiber.index > newIdx) {
1037
+ nextOldFiber = oldFiber;
1038
+ oldFiber = null;
1039
+ } else {
1040
+ nextOldFiber = oldFiber.sibling;
1041
+ }
1042
+ const newFiber = updateSlot(
1043
+ returnFiber,
1044
+ oldFiber,
1045
+ step.value,
1046
+ expirationTime,
1047
+ );
1048
+ if (newFiber === null) {
1049
+ // TODO: This breaks on empty slots like null children. That's
1050
+ // unfortunate because it triggers the slow path all the time. We need
1051
+ // a better way to communicate whether this was a miss or null,
1052
+ // boolean, undefined, etc.
1053
+ if (oldFiber === null) {
1054
+ oldFiber = nextOldFiber;
1055
+ }
1056
+ break;
1057
+ }
1058
+ if (shouldTrackSideEffects) {
1059
+ if (oldFiber && newFiber.alternate === null) {
1060
+ // We matched the slot, but we didn't reuse the existing fiber, so we
1061
+ // need to delete the existing child.
1062
+ deleteChild(returnFiber, oldFiber);
1063
+ }
1064
+ }
1065
+ lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
1066
+ if (previousNewFiber === null) {
1067
+ // TODO: Move out of the loop. This only happens for the first run.
1068
+ resultingFirstChild = newFiber;
1069
+ } else {
1070
+ // TODO: Defer siblings if we're not at the right index for this slot.
1071
+ // I.e. if we had null values before, then we want to defer this
1072
+ // for each null value. However, we also don't want to call updateSlot
1073
+ // with the previous one.
1074
+ previousNewFiber.sibling = newFiber;
1075
+ }
1076
+ previousNewFiber = newFiber;
1077
+ oldFiber = nextOldFiber;
1078
+ }
1079
+
1080
+ if (step.done) {
1081
+ // We've reached the end of the new children. We can delete the rest.
1082
+ deleteRemainingChildren(returnFiber, oldFiber);
1083
+ return resultingFirstChild;
1084
+ }
1085
+
1086
+ if (oldFiber === null) {
1087
+ // If we don't have any more existing children we can choose a fast path
1088
+ // since the rest will all be insertions.
1089
+ for (; !step.done; newIdx++, step = newChildren.next()) {
1090
+ const newFiber = createChild(returnFiber, step.value, expirationTime);
1091
+ if (newFiber === null) {
1092
+ continue;
1093
+ }
1094
+ lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
1095
+ if (previousNewFiber === null) {
1096
+ // TODO: Move out of the loop. This only happens for the first run.
1097
+ resultingFirstChild = newFiber;
1098
+ } else {
1099
+ previousNewFiber.sibling = newFiber;
1100
+ }
1101
+ previousNewFiber = newFiber;
1102
+ }
1103
+ return resultingFirstChild;
1104
+ }
1105
+
1106
+ // Add all children to a key map for quick lookups.
1107
+ const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
1108
+
1109
+ // Keep scanning and use the map to restore deleted items as moves.
1110
+ for (; !step.done; newIdx++, step = newChildren.next()) {
1111
+ const newFiber = updateFromMap(
1112
+ existingChildren,
1113
+ returnFiber,
1114
+ newIdx,
1115
+ step.value,
1116
+ expirationTime,
1117
+ );
1118
+ if (newFiber !== null) {
1119
+ if (shouldTrackSideEffects) {
1120
+ if (newFiber.alternate !== null) {
1121
+ // The new fiber is a work in progress, but if there exists a
1122
+ // current, that means that we reused the fiber. We need to delete
1123
+ // it from the child list so that we don't add it to the deletion
1124
+ // list.
1125
+ existingChildren.delete(
1126
+ newFiber.key === null ? newIdx : newFiber.key,
1127
+ );
1128
+ }
1129
+ }
1130
+ lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
1131
+ if (previousNewFiber === null) {
1132
+ resultingFirstChild = newFiber;
1133
+ } else {
1134
+ previousNewFiber.sibling = newFiber;
1135
+ }
1136
+ previousNewFiber = newFiber;
1137
+ }
1138
+ }
1139
+
1140
+ if (shouldTrackSideEffects) {
1141
+ // Any existing children that weren't consumed above were deleted. We need
1142
+ // to add them to the deletion list.
1143
+ existingChildren.forEach(child => deleteChild(returnFiber, child));
1144
+ }
1145
+
1146
+ return resultingFirstChild;
1147
+ }
1148
+
1149
+ function reconcileSingleTextNode(
1150
+ returnFiber: Fiber,
1151
+ currentFirstChild: Fiber | null,
1152
+ textContent: string,
1153
+ expirationTime: ExpirationTime,
1154
+ ): Fiber {
1155
+ // There's no need to check for keys on text nodes since we don't have a
1156
+ // way to define them.
1157
+ if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
1158
+ // We already have an existing node so let's just update it and delete
1159
+ // the rest.
1160
+ deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
1161
+ const existing = useFiber(currentFirstChild, textContent);
1162
+ existing.return = returnFiber;
1163
+ return existing;
1164
+ }
1165
+ // The existing first child is not a text node so we need to create one
1166
+ // and delete the existing ones.
1167
+ deleteRemainingChildren(returnFiber, currentFirstChild);
1168
+ const created = createFiberFromText(
1169
+ textContent,
1170
+ returnFiber.mode,
1171
+ expirationTime,
1172
+ );
1173
+ created.return = returnFiber;
1174
+ return created;
1175
+ }
1176
+
1177
+ function reconcileSingleElement(
1178
+ returnFiber: Fiber,
1179
+ currentFirstChild: Fiber | null,
1180
+ element: ReactElement,
1181
+ expirationTime: ExpirationTime,
1182
+ ): Fiber {
1183
+ const key = element.key;
1184
+ let child = currentFirstChild;
1185
+ while (child !== null) {
1186
+ // TODO: If key === null and child.key === null, then this only applies to
1187
+ // the first item in the list.
1188
+ if (child.key === key) {
1189
+ switch (child.tag) {
1190
+ case Fragment: {
1191
+ if (element.type === REACT_FRAGMENT_TYPE) {
1192
+ deleteRemainingChildren(returnFiber, child.sibling);
1193
+ const existing = useFiber(child, element.props.children);
1194
+ existing.return = returnFiber;
1195
+ if (__DEV__) {
1196
+ existing._debugSource = element._source;
1197
+ existing._debugOwner = element._owner;
1198
+ }
1199
+ return existing;
1200
+ }
1201
+ break;
1202
+ }
1203
+ case Block:
1204
+ if (enableBlocksAPI) {
1205
+ let type = element.type;
1206
+ if (type.$$typeof === REACT_LAZY_TYPE) {
1207
+ type = resolveLazyType(type);
1208
+ }
1209
+ if (type.$$typeof === REACT_BLOCK_TYPE) {
1210
+ // The new Block might not be initialized yet. We need to initialize
1211
+ // it in case initializing it turns out it would match.
1212
+ if (
1213
+ ((type: any): BlockComponent<any, any>)._render ===
1214
+ (child.type: BlockComponent<any, any>)._render
1215
+ ) {
1216
+ deleteRemainingChildren(returnFiber, child.sibling);
1217
+ const existing = useFiber(child, element.props);
1218
+ existing.type = type;
1219
+ existing.return = returnFiber;
1220
+ if (__DEV__) {
1221
+ existing._debugSource = element._source;
1222
+ existing._debugOwner = element._owner;
1223
+ }
1224
+ return existing;
1225
+ }
1226
+ }
1227
+ }
1228
+ // We intentionally fallthrough here if enableBlocksAPI is not on.
1229
+ // eslint-disable-next-lined no-fallthrough
1230
+ default: {
1231
+ if (
1232
+ child.elementType === element.type ||
1233
+ // Keep this check inline so it only runs on the false path:
1234
+ (__DEV__
1235
+ ? isCompatibleFamilyForHotReloading(child, element)
1236
+ : false)
1237
+ ) {
1238
+ deleteRemainingChildren(returnFiber, child.sibling);
1239
+ const existing = useFiber(child, element.props);
1240
+ existing.ref = coerceRef(returnFiber, child, element);
1241
+ existing.return = returnFiber;
1242
+ if (__DEV__) {
1243
+ existing._debugSource = element._source;
1244
+ existing._debugOwner = element._owner;
1245
+ }
1246
+ return existing;
1247
+ }
1248
+ break;
1249
+ }
1250
+ }
1251
+ // Didn't match.
1252
+ deleteRemainingChildren(returnFiber, child);
1253
+ break;
1254
+ } else {
1255
+ deleteChild(returnFiber, child);
1256
+ }
1257
+ child = child.sibling;
1258
+ }
1259
+
1260
+ if (element.type === REACT_FRAGMENT_TYPE) {
1261
+ const created = createFiberFromFragment(
1262
+ element.props.children,
1263
+ returnFiber.mode,
1264
+ expirationTime,
1265
+ element.key,
1266
+ );
1267
+ created.return = returnFiber;
1268
+ return created;
1269
+ } else {
1270
+ const created = createFiberFromElement(
1271
+ element,
1272
+ returnFiber.mode,
1273
+ expirationTime,
1274
+ );
1275
+ created.ref = coerceRef(returnFiber, currentFirstChild, element);
1276
+ created.return = returnFiber;
1277
+ return created;
1278
+ }
1279
+ }
1280
+
1281
+ function reconcileSinglePortal(
1282
+ returnFiber: Fiber,
1283
+ currentFirstChild: Fiber | null,
1284
+ portal: ReactPortal,
1285
+ expirationTime: ExpirationTime,
1286
+ ): Fiber {
1287
+ const key = portal.key;
1288
+ let child = currentFirstChild;
1289
+ while (child !== null) {
1290
+ // TODO: If key === null and child.key === null, then this only applies to
1291
+ // the first item in the list.
1292
+ if (child.key === key) {
1293
+ if (
1294
+ child.tag === HostPortal &&
1295
+ child.stateNode.containerInfo === portal.containerInfo &&
1296
+ child.stateNode.implementation === portal.implementation
1297
+ ) {
1298
+ deleteRemainingChildren(returnFiber, child.sibling);
1299
+ const existing = useFiber(child, portal.children || []);
1300
+ existing.return = returnFiber;
1301
+ return existing;
1302
+ } else {
1303
+ deleteRemainingChildren(returnFiber, child);
1304
+ break;
1305
+ }
1306
+ } else {
1307
+ deleteChild(returnFiber, child);
1308
+ }
1309
+ child = child.sibling;
1310
+ }
1311
+
1312
+ const created = createFiberFromPortal(
1313
+ portal,
1314
+ returnFiber.mode,
1315
+ expirationTime,
1316
+ );
1317
+ created.return = returnFiber;
1318
+ return created;
1319
+ }
1320
+
1321
+ // This API will tag the children with the side-effect of the reconciliation
1322
+ // itself. They will be added to the side-effect list as we pass through the
1323
+ // children and the parent.
1324
+ function reconcileChildFibers(
1325
+ returnFiber: Fiber,
1326
+ currentFirstChild: Fiber | null,
1327
+ newChild: any,
1328
+ expirationTime: ExpirationTime,
1329
+ ): Fiber | null {
1330
+ // This function is not recursive.
1331
+ // If the top level item is an array, we treat it as a set of children,
1332
+ // not as a fragment. Nested arrays on the other hand will be treated as
1333
+ // fragment nodes. Recursion happens at the normal flow.
1334
+
1335
+ // Handle top level unkeyed fragments as if they were arrays.
1336
+ // This leads to an ambiguity between <>{[...]}</> and <>...</>.
1337
+ // We treat the ambiguous cases above the same.
1338
+ const isUnkeyedTopLevelFragment =
1339
+ typeof newChild === 'object' &&
1340
+ newChild !== null &&
1341
+ newChild.type === REACT_FRAGMENT_TYPE &&
1342
+ newChild.key === null;
1343
+ if (isUnkeyedTopLevelFragment) {
1344
+ newChild = newChild.props.children;
1345
+ }
1346
+
1347
+ // Handle object types
1348
+ const isObject = typeof newChild === 'object' && newChild !== null;
1349
+
1350
+ if (isObject) {
1351
+ switch (newChild.$$typeof) {
1352
+ case REACT_ELEMENT_TYPE:
1353
+ return placeSingleChild(
1354
+ reconcileSingleElement(
1355
+ returnFiber,
1356
+ currentFirstChild,
1357
+ newChild,
1358
+ expirationTime,
1359
+ ),
1360
+ );
1361
+ case REACT_PORTAL_TYPE:
1362
+ return placeSingleChild(
1363
+ reconcileSinglePortal(
1364
+ returnFiber,
1365
+ currentFirstChild,
1366
+ newChild,
1367
+ expirationTime,
1368
+ ),
1369
+ );
1370
+ }
1371
+ }
1372
+
1373
+ if (typeof newChild === 'string' || typeof newChild === 'number') {
1374
+ return placeSingleChild(
1375
+ reconcileSingleTextNode(
1376
+ returnFiber,
1377
+ currentFirstChild,
1378
+ '' + newChild,
1379
+ expirationTime,
1380
+ ),
1381
+ );
1382
+ }
1383
+
1384
+ if (isArray(newChild)) {
1385
+ return reconcileChildrenArray(
1386
+ returnFiber,
1387
+ currentFirstChild,
1388
+ newChild,
1389
+ expirationTime,
1390
+ );
1391
+ }
1392
+
1393
+ if (getIteratorFn(newChild)) {
1394
+ return reconcileChildrenIterator(
1395
+ returnFiber,
1396
+ currentFirstChild,
1397
+ newChild,
1398
+ expirationTime,
1399
+ );
1400
+ }
1401
+
1402
+ if (isObject) {
1403
+ throwOnInvalidObjectType(returnFiber, newChild);
1404
+ }
1405
+
1406
+ if (__DEV__) {
1407
+ if (typeof newChild === 'function') {
1408
+ warnOnFunctionType();
1409
+ }
1410
+ }
1411
+ if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
1412
+ // If the new child is undefined, and the return fiber is a composite
1413
+ // component, throw an error. If Fiber return types are disabled,
1414
+ // we already threw above.
1415
+ switch (returnFiber.tag) {
1416
+ case ClassComponent: {
1417
+ if (__DEV__) {
1418
+ const instance = returnFiber.stateNode;
1419
+ if (instance.render._isMockFunction) {
1420
+ // We allow auto-mocks to proceed as if they're returning null.
1421
+ break;
1422
+ }
1423
+ }
1424
+ }
1425
+ // Intentionally fall through to the next case, which handles both
1426
+ // functions and classes
1427
+ // eslint-disable-next-lined no-fallthrough
1428
+ case FunctionComponent: {
1429
+ const Component = returnFiber.type;
1430
+ invariant(
1431
+ false,
1432
+ '%s(...): Nothing was returned from render. This usually means a ' +
1433
+ 'return statement is missing. Or, to render nothing, ' +
1434
+ 'return null.',
1435
+ Component.displayName || Component.name || 'Component',
1436
+ );
1437
+ }
1438
+ }
1439
+ }
1440
+
1441
+ // Remaining cases are all treated as empty.
1442
+ return deleteRemainingChildren(returnFiber, currentFirstChild);
1443
+ }
1444
+
1445
+ return reconcileChildFibers;
1446
+}
1447
+
1448
+export const reconcileChildFibers = ChildReconciler(true);
1449
+export const mountChildFibers = ChildReconciler(false);
1450
+
1451
+export function cloneChildFibers(
1452
+ current: Fiber | null,
1453
+ workInProgress: Fiber,
1454
+): void {
1455
+ invariant(
1456
+ current === null || workInProgress.child === current.child,
1457
+ 'Resuming work not yet implemented.',
1458
+ );
1459
+
1460
+ if (workInProgress.child === null) {
1461
+ return;
1462
+ }
1463
+
1464
+ let currentChild = workInProgress.child;
1465
+ let newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
1466
+ workInProgress.child = newChild;
1467
+
1468
+ newChild.return = workInProgress;
1469
+ while (currentChild.sibling !== null) {
1470
+ currentChild = currentChild.sibling;
1471
+ newChild = newChild.sibling = createWorkInProgress(
1472
+ currentChild,
1473
+ currentChild.pendingProps,
1474
+ );
1475
+ newChild.return = workInProgress;
1476
+ }
1477
+ newChild.sibling = null;
1478
+}
1479
+
1480
+// Reset a workInProgress child set to prepare it for a second pass.
1481
+export function resetChildFibers(
1482
+ workInProgress: Fiber,
1483
+ renderExpirationTime: ExpirationTime,
1484
+): void {
1485
+ let child = workInProgress.child;
1486
+ while (child !== null) {
1487
+ resetWorkInProgress(child, renderExpirationTime);
1488
+ child = child.sibling;
1489
+ }
1490
+}
packages/react-reconciler/src/ReactFiber.new.js
new
+817
@@ -0,0 +1,817 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {ReactElement} from 'shared/ReactElementType';
11
+import type {
12
+ ReactFragment,
13
+ ReactPortal,
14
+ ReactFundamentalComponent,
15
+ ReactScope,
16
+} from 'shared/ReactTypes';
17
+import type {Fiber} from './ReactInternalTypes';
18
+import type {RootTag} from './ReactRootTags';
19
+import type {WorkTag} from './ReactWorkTags';
20
+import type {TypeOfMode} from './ReactTypeOfMode';
21
+import type {ExpirationTime} from './ReactFiberExpirationTime';
22
+import type {SuspenseInstance} from './ReactFiberHostConfig';
23
+
24
+import invariant from 'shared/invariant';
25
+import {
26
+ enableProfilerTimer,
27
+ enableFundamentalAPI,
28
+ enableScopeAPI,
29
+ enableBlocksAPI,
30
+ throwEarlyForMysteriousError,
31
+} from 'shared/ReactFeatureFlags';
32
+import {NoEffect, Placement} from './ReactSideEffectTags';
33
+import {ConcurrentRoot, BlockingRoot} from './ReactRootTags';
34
+import {
35
+ IndeterminateComponent,
36
+ ClassComponent,
37
+ HostRoot,
38
+ HostComponent,
39
+ HostText,
40
+ HostPortal,
41
+ ForwardRef,
42
+ Fragment,
43
+ Mode,
44
+ ContextProvider,
45
+ ContextConsumer,
46
+ Profiler,
47
+ SuspenseComponent,
48
+ SuspenseListComponent,
49
+ DehydratedFragment,
50
+ FunctionComponent,
51
+ MemoComponent,
52
+ SimpleMemoComponent,
53
+ LazyComponent,
54
+ FundamentalComponent,
55
+ ScopeComponent,
56
+ Block,
57
+} from './ReactWorkTags';
58
+import getComponentName from 'shared/getComponentName';
59
+
60
+import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
61
+import {
62
+ resolveClassForHotReloading,
63
+ resolveFunctionForHotReloading,
64
+ resolveForwardRefForHotReloading,
65
+} from './ReactFiberHotReloading.new';
66
+import {NoWork} from './ReactFiberExpirationTime';
67
+import {
68
+ NoMode,
69
+ ConcurrentMode,
70
+ ProfileMode,
71
+ StrictMode,
72
+ BlockingMode,
73
+} from './ReactTypeOfMode';
74
+import {
75
+ REACT_FORWARD_REF_TYPE,
76
+ REACT_FRAGMENT_TYPE,
77
+ REACT_STRICT_MODE_TYPE,
78
+ REACT_PROFILER_TYPE,
79
+ REACT_PROVIDER_TYPE,
80
+ REACT_CONTEXT_TYPE,
81
+ REACT_SUSPENSE_TYPE,
82
+ REACT_SUSPENSE_LIST_TYPE,
83
+ REACT_MEMO_TYPE,
84
+ REACT_LAZY_TYPE,
85
+ REACT_FUNDAMENTAL_TYPE,
86
+ REACT_SCOPE_TYPE,
87
+ REACT_BLOCK_TYPE,
88
+} from 'shared/ReactSymbols';
89
+
90
+export type {Fiber};
91
+
92
+let hasBadMapPolyfill;
93
+
94
+if (__DEV__) {
95
+ hasBadMapPolyfill = false;
96
+ try {
97
+ const nonExtensibleObject = Object.preventExtensions({});
98
+ /* eslint-disable no-new */
99
+ new Map([[nonExtensibleObject, null]]);
100
+ new Set([nonExtensibleObject]);
101
+ /* eslint-enable no-new */
102
+ } catch (e) {
103
+ // TODO: Consider warning about bad polyfills
104
+ hasBadMapPolyfill = true;
105
+ }
106
+}
107
+
108
+let debugCounter = 1;
109
+
110
+function FiberNode(
111
+ tag: WorkTag,
112
+ pendingProps: mixed,
113
+ key: null | string,
114
+ mode: TypeOfMode,
115
+) {
116
+ // Instance
117
+ this.tag = tag;
118
+ this.key = key;
119
+ this.elementType = null;
120
+ this.type = null;
121
+ this.stateNode = null;
122
+
123
+ // Fiber
124
+ this.return = null;
125
+ this.child = null;
126
+ this.sibling = null;
127
+ this.index = 0;
128
+
129
+ this.ref = null;
130
+
131
+ this.pendingProps = pendingProps;
132
+ this.memoizedProps = null;
133
+ this.updateQueue = null;
134
+ this.memoizedState = null;
135
+ this.dependencies = null;
136
+
137
+ this.mode = mode;
138
+
139
+ // Effects
140
+ this.effectTag = NoEffect;
141
+ this.nextEffect = null;
142
+
143
+ this.firstEffect = null;
144
+ this.lastEffect = null;
145
+
146
+ this.expirationTime = NoWork;
147
+ this.childExpirationTime = NoWork;
148
+
149
+ this.alternate = null;
150
+
151
+ if (enableProfilerTimer) {
152
+ // Note: The following is done to avoid a v8 performance cliff.
153
+ //
154
+ // Initializing the fields below to smis and later updating them with
155
+ // double values will cause Fibers to end up having separate shapes.
156
+ // This behavior/bug has something to do with Object.preventExtension().
157
+ // Fortunately this only impacts DEV builds.
158
+ // Unfortunately it makes React unusably slow for some applications.
159
+ // To work around this, initialize the fields below with doubles.
160
+ //
161
+ // Learn more about this here:
162
+ // https://github.com/facebook/react/issues/14365
163
+ // https://bugs.chromium.org/p/v8/issues/detail?id=8538
164
+ this.actualDuration = Number.NaN;
165
+ this.actualStartTime = Number.NaN;
166
+ this.selfBaseDuration = Number.NaN;
167
+ this.treeBaseDuration = Number.NaN;
168
+
169
+ // It's okay to replace the initial doubles with smis after initialization.
170
+ // This won't trigger the performance cliff mentioned above,
171
+ // and it simplifies other profiler code (including DevTools).
172
+ this.actualDuration = 0;
173
+ this.actualStartTime = -1;
174
+ this.selfBaseDuration = 0;
175
+ this.treeBaseDuration = 0;
176
+ }
177
+
178
+ if (__DEV__) {
179
+ // This isn't directly used but is handy for debugging internals:
180
+ this._debugID = debugCounter++;
181
+ this._debugSource = null;
182
+ this._debugOwner = null;
183
+ this._debugNeedsRemount = false;
184
+ this._debugHookTypes = null;
185
+ if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
186
+ Object.preventExtensions(this);
187
+ }
188
+ }
189
+}
190
+
191
+// This is a constructor function, rather than a POJO constructor, still
192
+// please ensure we do the following:
193
+// 1) Nobody should add any instance methods on this. Instance methods can be
194
+// more difficult to predict when they get optimized and they are almost
195
+// never inlined properly in static compilers.
196
+// 2) Nobody should rely on `instanceof Fiber` for type testing. We should
197
+// always know when it is a fiber.
198
+// 3) We might want to experiment with using numeric keys since they are easier
199
+// to optimize in a non-JIT environment.
200
+// 4) We can easily go from a constructor to a createFiber object literal if that
201
+// is faster.
202
+// 5) It should be easy to port this to a C struct and keep a C implementation
203
+// compatible.
204
+const createFiber = function(
205
+ tag: WorkTag,
206
+ pendingProps: mixed,
207
+ key: null | string,
208
+ mode: TypeOfMode,
209
+): Fiber {
210
+ // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
211
+ return new FiberNode(tag, pendingProps, key, mode);
212
+};
213
+
214
+function shouldConstruct(Component: Function) {
215
+ const prototype = Component.prototype;
216
+ return !!(prototype && prototype.isReactComponent);
217
+}
218
+
219
+export function isSimpleFunctionComponent(type: any) {
220
+ return (
221
+ typeof type === 'function' &&
222
+ !shouldConstruct(type) &&
223
+ type.defaultProps === undefined
224
+ );
225
+}
226
+
227
+export function resolveLazyComponentTag(Component: Function): WorkTag {
228
+ if (typeof Component === 'function') {
229
+ return shouldConstruct(Component) ? ClassComponent : FunctionComponent;
230
+ } else if (Component !== undefined && Component !== null) {
231
+ const $$typeof = Component.$$typeof;
232
+ if ($$typeof === REACT_FORWARD_REF_TYPE) {
233
+ return ForwardRef;
234
+ }
235
+ if ($$typeof === REACT_MEMO_TYPE) {
236
+ return MemoComponent;
237
+ }
238
+ if (enableBlocksAPI) {
239
+ if ($$typeof === REACT_BLOCK_TYPE) {
240
+ return Block;
241
+ }
242
+ }
243
+ }
244
+ return IndeterminateComponent;
245
+}
246
+
247
+// This is used to create an alternate fiber to do work on.
248
+export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
249
+ let workInProgress = current.alternate;
250
+ if (workInProgress === null) {
251
+ // We use a double buffering pooling technique because we know that we'll
252
+ // only ever need at most two versions of a tree. We pool the "other" unused
253
+ // node that we're free to reuse. This is lazily created to avoid allocating
254
+ // extra objects for things that are never updated. It also allow us to
255
+ // reclaim the extra memory if needed.
256
+ workInProgress = createFiber(
257
+ current.tag,
258
+ pendingProps,
259
+ current.key,
260
+ current.mode,
261
+ );
262
+ workInProgress.elementType = current.elementType;
263
+ workInProgress.type = current.type;
264
+ workInProgress.stateNode = current.stateNode;
265
+
266
+ if (__DEV__) {
267
+ // DEV-only fields
268
+ workInProgress._debugID = current._debugID;
269
+ workInProgress._debugSource = current._debugSource;
270
+ workInProgress._debugOwner = current._debugOwner;
271
+ workInProgress._debugHookTypes = current._debugHookTypes;
272
+ }
273
+
274
+ workInProgress.alternate = current;
275
+ current.alternate = workInProgress;
276
+ } else {
277
+ workInProgress.pendingProps = pendingProps;
278
+
279
+ // We already have an alternate.
280
+ // Reset the effect tag.
281
+ workInProgress.effectTag = NoEffect;
282
+
283
+ // The effect list is no longer valid.
284
+ workInProgress.nextEffect = null;
285
+ workInProgress.firstEffect = null;
286
+ workInProgress.lastEffect = null;
287
+
288
+ if (enableProfilerTimer) {
289
+ // We intentionally reset, rather than copy, actualDuration & actualStartTime.
290
+ // This prevents time from endlessly accumulating in new commits.
291
+ // This has the downside of resetting values for different priority renders,
292
+ // But works for yielding (the common case) and should support resuming.
293
+ workInProgress.actualDuration = 0;
294
+ workInProgress.actualStartTime = -1;
295
+ }
296
+ }
297
+
298
+ if (throwEarlyForMysteriousError) {
299
+ // Trying to debug a mysterious internal-only production failure.
300
+ // See D20130868 and t62461245.
301
+ // This is only on for RN FB builds.
302
+ if (current == null) {
303
+ throw Error('current is ' + current + " but it can't be");
304
+ }
305
+ if (workInProgress == null) {
306
+ throw Error('workInProgress is ' + workInProgress + " but it can't be");
307
+ }
308
+ }
309
+
310
+ workInProgress.childExpirationTime = current.childExpirationTime;
311
+ workInProgress.expirationTime = current.expirationTime;
312
+
313
+ workInProgress.child = current.child;
314
+ workInProgress.memoizedProps = current.memoizedProps;
315
+ workInProgress.memoizedState = current.memoizedState;
316
+ workInProgress.updateQueue = current.updateQueue;
317
+
318
+ // Clone the dependencies object. This is mutated during the render phase, so
319
+ // it cannot be shared with the current fiber.
320
+ const currentDependencies = current.dependencies;
321
+ workInProgress.dependencies =
322
+ currentDependencies === null
323
+ ? null
324
+ : {
325
+ expirationTime: currentDependencies.expirationTime,
326
+ firstContext: currentDependencies.firstContext,
327
+ responders: currentDependencies.responders,
328
+ };
329
+
330
+ // These will be overridden during the parent's reconciliation
331
+ workInProgress.sibling = current.sibling;
332
+ workInProgress.index = current.index;
333
+ workInProgress.ref = current.ref;
334
+
335
+ if (enableProfilerTimer) {
336
+ workInProgress.selfBaseDuration = current.selfBaseDuration;
337
+ workInProgress.treeBaseDuration = current.treeBaseDuration;
338
+ }
339
+
340
+ if (__DEV__) {
341
+ workInProgress._debugNeedsRemount = current._debugNeedsRemount;
342
+ switch (workInProgress.tag) {
343
+ case IndeterminateComponent:
344
+ case FunctionComponent:
345
+ case SimpleMemoComponent:
346
+ workInProgress.type = resolveFunctionForHotReloading(current.type);
347
+ break;
348
+ case ClassComponent:
349
+ workInProgress.type = resolveClassForHotReloading(current.type);
350
+ break;
351
+ case ForwardRef:
352
+ workInProgress.type = resolveForwardRefForHotReloading(current.type);
353
+ break;
354
+ default:
355
+ break;
356
+ }
357
+ }
358
+
359
+ return workInProgress;
360
+}
361
+
362
+// Used to reuse a Fiber for a second pass.
363
+export function resetWorkInProgress(
364
+ workInProgress: Fiber,
365
+ renderExpirationTime: ExpirationTime,
366
+) {
367
+ // This resets the Fiber to what createFiber or createWorkInProgress would
368
+ // have set the values to before during the first pass. Ideally this wouldn't
369
+ // be necessary but unfortunately many code paths reads from the workInProgress
370
+ // when they should be reading from current and writing to workInProgress.
371
+
372
+ // We assume pendingProps, index, key, ref, return are still untouched to
373
+ // avoid doing another reconciliation.
374
+
375
+ // Reset the effect tag but keep any Placement tags, since that's something
376
+ // that child fiber is setting, not the reconciliation.
377
+ workInProgress.effectTag &= Placement;
378
+
379
+ // The effect list is no longer valid.
380
+ workInProgress.nextEffect = null;
381
+ workInProgress.firstEffect = null;
382
+ workInProgress.lastEffect = null;
383
+
384
+ const current = workInProgress.alternate;
385
+ if (current === null) {
386
+ // Reset to createFiber's initial values.
387
+ workInProgress.childExpirationTime = NoWork;
388
+ workInProgress.expirationTime = renderExpirationTime;
389
+
390
+ workInProgress.child = null;
391
+ workInProgress.memoizedProps = null;
392
+ workInProgress.memoizedState = null;
393
+ workInProgress.updateQueue = null;
394
+
395
+ workInProgress.dependencies = null;
396
+
397
+ workInProgress.stateNode = null;
398
+
399
+ if (enableProfilerTimer) {
400
+ // Note: We don't reset the actualTime counts. It's useful to accumulate
401
+ // actual time across multiple render passes.
402
+ workInProgress.selfBaseDuration = 0;
403
+ workInProgress.treeBaseDuration = 0;
404
+ }
405
+ } else {
406
+ // Reset to the cloned values that createWorkInProgress would've.
407
+ workInProgress.childExpirationTime = current.childExpirationTime;
408
+ workInProgress.expirationTime = current.expirationTime;
409
+
410
+ workInProgress.child = current.child;
411
+ workInProgress.memoizedProps = current.memoizedProps;
412
+ workInProgress.memoizedState = current.memoizedState;
413
+ workInProgress.updateQueue = current.updateQueue;
414
+
415
+ // Clone the dependencies object. This is mutated during the render phase, so
416
+ // it cannot be shared with the current fiber.
417
+ const currentDependencies = current.dependencies;
418
+ workInProgress.dependencies =
419
+ currentDependencies === null
420
+ ? null
421
+ : {
422
+ expirationTime: currentDependencies.expirationTime,
423
+ firstContext: currentDependencies.firstContext,
424
+ responders: currentDependencies.responders,
425
+ };
426
+
427
+ if (enableProfilerTimer) {
428
+ // Note: We don't reset the actualTime counts. It's useful to accumulate
429
+ // actual time across multiple render passes.
430
+ workInProgress.selfBaseDuration = current.selfBaseDuration;
431
+ workInProgress.treeBaseDuration = current.treeBaseDuration;
432
+ }
433
+ }
434
+
435
+ return workInProgress;
436
+}
437
+
438
+export function createHostRootFiber(tag: RootTag): Fiber {
439
+ let mode;
440
+ if (tag === ConcurrentRoot) {
441
+ mode = ConcurrentMode | BlockingMode | StrictMode;
442
+ } else if (tag === BlockingRoot) {
443
+ mode = BlockingMode | StrictMode;
444
+ } else {
445
+ mode = NoMode;
446
+ }
447
+
448
+ if (enableProfilerTimer && isDevToolsPresent) {
449
+ // Always collect profile timings when DevTools are present.
450
+ // This enables DevTools to start capturing timing at any point–
451
+ // Without some nodes in the tree having empty base times.
452
+ mode |= ProfileMode;
453
+ }
454
+
455
+ return createFiber(HostRoot, null, null, mode);
456
+}
457
+
458
+export function createFiberFromTypeAndProps(
459
+ type: any, // React$ElementType
460
+ key: null | string,
461
+ pendingProps: any,
462
+ owner: null | Fiber,
463
+ mode: TypeOfMode,
464
+ expirationTime: ExpirationTime,
465
+): Fiber {
466
+ let fiberTag = IndeterminateComponent;
467
+ // The resolved type is set if we know what the final type will be. I.e. it's not lazy.
468
+ let resolvedType = type;
469
+ if (typeof type === 'function') {
470
+ if (shouldConstruct(type)) {
471
+ fiberTag = ClassComponent;
472
+ if (__DEV__) {
473
+ resolvedType = resolveClassForHotReloading(resolvedType);
474
+ }
475
+ } else {
476
+ if (__DEV__) {
477
+ resolvedType = resolveFunctionForHotReloading(resolvedType);
478
+ }
479
+ }
480
+ } else if (typeof type === 'string') {
481
+ fiberTag = HostComponent;
482
+ } else {
483
+ getTag: switch (type) {
484
+ case REACT_FRAGMENT_TYPE:
485
+ return createFiberFromFragment(
486
+ pendingProps.children,
487
+ mode,
488
+ expirationTime,
489
+ key,
490
+ );
491
+ case REACT_STRICT_MODE_TYPE:
492
+ fiberTag = Mode;
493
+ mode |= StrictMode;
494
+ break;
495
+ case REACT_PROFILER_TYPE:
496
+ return createFiberFromProfiler(pendingProps, mode, expirationTime, key);
497
+ case REACT_SUSPENSE_TYPE:
498
+ return createFiberFromSuspense(pendingProps, mode, expirationTime, key);
499
+ case REACT_SUSPENSE_LIST_TYPE:
500
+ return createFiberFromSuspenseList(
501
+ pendingProps,
502
+ mode,
503
+ expirationTime,
504
+ key,
505
+ );
506
+ default: {
507
+ if (typeof type === 'object' && type !== null) {
508
+ switch (type.$$typeof) {
509
+ case REACT_PROVIDER_TYPE:
510
+ fiberTag = ContextProvider;
511
+ break getTag;
512
+ case REACT_CONTEXT_TYPE:
513
+ // This is a consumer
514
+ fiberTag = ContextConsumer;
515
+ break getTag;
516
+ case REACT_FORWARD_REF_TYPE:
517
+ fiberTag = ForwardRef;
518
+ if (__DEV__) {
519
+ resolvedType = resolveForwardRefForHotReloading(resolvedType);
520
+ }
521
+ break getTag;
522
+ case REACT_MEMO_TYPE:
523
+ fiberTag = MemoComponent;
524
+ break getTag;
525
+ case REACT_LAZY_TYPE:
526
+ fiberTag = LazyComponent;
527
+ resolvedType = null;
528
+ break getTag;
529
+ case REACT_BLOCK_TYPE:
530
+ fiberTag = Block;
531
+ break getTag;
532
+ case REACT_FUNDAMENTAL_TYPE:
533
+ if (enableFundamentalAPI) {
534
+ return createFiberFromFundamental(
535
+ type,
536
+ pendingProps,
537
+ mode,
538
+ expirationTime,
539
+ key,
540
+ );
541
+ }
542
+ break;
543
+ case REACT_SCOPE_TYPE:
544
+ if (enableScopeAPI) {
545
+ return createFiberFromScope(
546
+ type,
547
+ pendingProps,
548
+ mode,
549
+ expirationTime,
550
+ key,
551
+ );
552
+ }
553
+ }
554
+ }
555
+ let info = '';
556
+ if (__DEV__) {
557
+ if (
558
+ type === undefined ||
559
+ (typeof type === 'object' &&
560
+ type !== null &&
561
+ Object.keys(type).length === 0)
562
+ ) {
563
+ info +=
564
+ ' You likely forgot to export your component from the file ' +
565
+ "it's defined in, or you might have mixed up default and " +
566
+ 'named imports.';
567
+ }
568
+ const ownerName = owner ? getComponentName(owner.type) : null;
569
+ if (ownerName) {
570
+ info += '\n\nCheck the render method of `' + ownerName + '`.';
571
+ }
572
+ }
573
+ invariant(
574
+ false,
575
+ 'Element type is invalid: expected a string (for built-in ' +
576
+ 'components) or a class/function (for composite components) ' +
577
+ 'but got: %s.%s',
578
+ type == null ? type : typeof type,
579
+ info,
580
+ );
581
+ }
582
+ }
583
+ }
584
+
585
+ const fiber = createFiber(fiberTag, pendingProps, key, mode);
586
+ fiber.elementType = type;
587
+ fiber.type = resolvedType;
588
+ fiber.expirationTime = expirationTime;
589
+
590
+ return fiber;
591
+}
592
+
593
+export function createFiberFromElement(
594
+ element: ReactElement,
595
+ mode: TypeOfMode,
596
+ expirationTime: ExpirationTime,
597
+): Fiber {
598
+ let owner = null;
599
+ if (__DEV__) {
600
+ owner = element._owner;
601
+ }
602
+ const type = element.type;
603
+ const key = element.key;
604
+ const pendingProps = element.props;
605
+ const fiber = createFiberFromTypeAndProps(
606
+ type,
607
+ key,
608
+ pendingProps,
609
+ owner,
610
+ mode,
611
+ expirationTime,
612
+ );
613
+ if (__DEV__) {
614
+ fiber._debugSource = element._source;
615
+ fiber._debugOwner = element._owner;
616
+ }
617
+ return fiber;
618
+}
619
+
620
+export function createFiberFromFragment(
621
+ elements: ReactFragment,
622
+ mode: TypeOfMode,
623
+ expirationTime: ExpirationTime,
624
+ key: null | string,
625
+): Fiber {
626
+ const fiber = createFiber(Fragment, elements, key, mode);
627
+ fiber.expirationTime = expirationTime;
628
+ return fiber;
629
+}
630
+
631
+export function createFiberFromFundamental(
632
+ fundamentalComponent: ReactFundamentalComponent<any, any>,
633
+ pendingProps: any,
634
+ mode: TypeOfMode,
635
+ expirationTime: ExpirationTime,
636
+ key: null | string,
637
+): Fiber {
638
+ const fiber = createFiber(FundamentalComponent, pendingProps, key, mode);
639
+ fiber.elementType = fundamentalComponent;
640
+ fiber.type = fundamentalComponent;
641
+ fiber.expirationTime = expirationTime;
642
+ return fiber;
643
+}
644
+
645
+function createFiberFromScope(
646
+ scope: ReactScope,
647
+ pendingProps: any,
648
+ mode: TypeOfMode,
649
+ expirationTime: ExpirationTime,
650
+ key: null | string,
651
+) {
652
+ const fiber = createFiber(ScopeComponent, pendingProps, key, mode);
653
+ fiber.type = scope;
654
+ fiber.elementType = scope;
655
+ fiber.expirationTime = expirationTime;
656
+ return fiber;
657
+}
658
+
659
+function createFiberFromProfiler(
660
+ pendingProps: any,
661
+ mode: TypeOfMode,
662
+ expirationTime: ExpirationTime,
663
+ key: null | string,
664
+): Fiber {
665
+ if (__DEV__) {
666
+ if (typeof pendingProps.id !== 'string') {
667
+ console.error('Profiler must specify an "id" as a prop');
668
+ }
669
+ }
670
+
671
+ const fiber = createFiber(Profiler, pendingProps, key, mode | ProfileMode);
672
+ // TODO: The Profiler fiber shouldn't have a type. It has a tag.
673
+ fiber.elementType = REACT_PROFILER_TYPE;
674
+ fiber.type = REACT_PROFILER_TYPE;
675
+ fiber.expirationTime = expirationTime;
676
+
677
+ if (enableProfilerTimer) {
678
+ fiber.stateNode = {
679
+ effectDuration: 0,
680
+ passiveEffectDuration: 0,
681
+ };
682
+ }
683
+
684
+ return fiber;
685
+}
686
+
687
+export function createFiberFromSuspense(
688
+ pendingProps: any,
689
+ mode: TypeOfMode,
690
+ expirationTime: ExpirationTime,
691
+ key: null | string,
692
+) {
693
+ const fiber = createFiber(SuspenseComponent, pendingProps, key, mode);
694
+
695
+ // TODO: The SuspenseComponent fiber shouldn't have a type. It has a tag.
696
+ // This needs to be fixed in getComponentName so that it relies on the tag
697
+ // instead.
698
+ fiber.type = REACT_SUSPENSE_TYPE;
699
+ fiber.elementType = REACT_SUSPENSE_TYPE;
700
+
701
+ fiber.expirationTime = expirationTime;
702
+ return fiber;
703
+}
704
+
705
+export function createFiberFromSuspenseList(
706
+ pendingProps: any,
707
+ mode: TypeOfMode,
708
+ expirationTime: ExpirationTime,
709
+ key: null | string,
710
+) {
711
+ const fiber = createFiber(SuspenseListComponent, pendingProps, key, mode);
712
+ if (__DEV__) {
713
+ // TODO: The SuspenseListComponent fiber shouldn't have a type. It has a tag.
714
+ // This needs to be fixed in getComponentName so that it relies on the tag
715
+ // instead.
716
+ fiber.type = REACT_SUSPENSE_LIST_TYPE;
717
+ }
718
+ fiber.elementType = REACT_SUSPENSE_LIST_TYPE;
719
+ fiber.expirationTime = expirationTime;
720
+ return fiber;
721
+}
722
+
723
+export function createFiberFromText(
724
+ content: string,
725
+ mode: TypeOfMode,
726
+ expirationTime: ExpirationTime,
727
+): Fiber {
728
+ const fiber = createFiber(HostText, content, null, mode);
729
+ fiber.expirationTime = expirationTime;
730
+ return fiber;
731
+}
732
+
733
+export function createFiberFromHostInstanceForDeletion(): Fiber {
734
+ const fiber = createFiber(HostComponent, null, null, NoMode);
735
+ // TODO: These should not need a type.
736
+ fiber.elementType = 'DELETED';
737
+ fiber.type = 'DELETED';
738
+ return fiber;
739
+}
740
+
741
+export function createFiberFromDehydratedFragment(
742
+ dehydratedNode: SuspenseInstance,
743
+): Fiber {
744
+ const fiber = createFiber(DehydratedFragment, null, null, NoMode);
745
+ fiber.stateNode = dehydratedNode;
746
+ return fiber;
747
+}
748
+
749
+export function createFiberFromPortal(
750
+ portal: ReactPortal,
751
+ mode: TypeOfMode,
752
+ expirationTime: ExpirationTime,
753
+): Fiber {
754
+ const pendingProps = portal.children !== null ? portal.children : [];
755
+ const fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
756
+ fiber.expirationTime = expirationTime;
757
+ fiber.stateNode = {
758
+ containerInfo: portal.containerInfo,
759
+ pendingChildren: null, // Used by persistent updates
760
+ implementation: portal.implementation,
761
+ };
762
+ return fiber;
763
+}
764
+
765
+// Used for stashing WIP properties to replay failed work in DEV.
766
+export function assignFiberPropertiesInDEV(
767
+ target: Fiber | null,
768
+ source: Fiber,
769
+): Fiber {
770
+ if (target === null) {
771
+ // This Fiber's initial properties will always be overwritten.
772
+ // We only use a Fiber to ensure the same hidden class so DEV isn't slow.
773
+ target = createFiber(IndeterminateComponent, null, null, NoMode);
774
+ }
775
+
776
+ // This is intentionally written as a list of all properties.
777
+ // We tried to use Object.assign() instead but this is called in
778
+ // the hottest path, and Object.assign() was too slow:
779
+ // https://github.com/facebook/react/issues/12502
780
+ // This code is DEV-only so size is not a concern.
781
+
782
+ target.tag = source.tag;
783
+ target.key = source.key;
784
+ target.elementType = source.elementType;
785
+ target.type = source.type;
786
+ target.stateNode = source.stateNode;
787
+ target.return = source.return;
788
+ target.child = source.child;
789
+ target.sibling = source.sibling;
790
+ target.index = source.index;
791
+ target.ref = source.ref;
792
+ target.pendingProps = source.pendingProps;
793
+ target.memoizedProps = source.memoizedProps;
794
+ target.updateQueue = source.updateQueue;
795
+ target.memoizedState = source.memoizedState;
796
+ target.dependencies = source.dependencies;
797
+ target.mode = source.mode;
798
+ target.effectTag = source.effectTag;
799
+ target.nextEffect = source.nextEffect;
800
+ target.firstEffect = source.firstEffect;
801
+ target.lastEffect = source.lastEffect;
802
+ target.expirationTime = source.expirationTime;
803
+ target.childExpirationTime = source.childExpirationTime;
804
+ target.alternate = source.alternate;
805
+ if (enableProfilerTimer) {
806
+ target.actualDuration = source.actualDuration;
807
+ target.actualStartTime = source.actualStartTime;
808
+ target.selfBaseDuration = source.selfBaseDuration;
809
+ target.treeBaseDuration = source.treeBaseDuration;
810
+ }
811
+ target._debugID = source._debugID;
812
+ target._debugSource = source._debugSource;
813
+ target._debugOwner = source._debugOwner;
814
+ target._debugNeedsRemount = source._debugNeedsRemount;
815
+ target._debugHookTypes = source._debugHookTypes;
816
+ return target;
817
+}
packages/react-reconciler/src/ReactFiberBeginWork.new.js
new
+3494
@@ -0,0 +1,3494 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
11
+import type {BlockComponent} from 'react/src/ReactBlock';
12
+import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
13
+import type {Fiber} from './ReactInternalTypes';
14
+import type {FiberRoot} from './ReactInternalTypes';
15
+import type {ExpirationTime} from './ReactFiberExpirationTime';
16
+import type {
17
+ SuspenseState,
18
+ SuspenseListRenderState,
19
+ SuspenseListTailMode,
20
+} from './ReactFiberSuspenseComponent.new';
21
+import type {SuspenseContext} from './ReactFiberSuspenseContext.new';
22
+
23
+import checkPropTypes from 'shared/checkPropTypes';
24
+
25
+import {
26
+ IndeterminateComponent,
27
+ FunctionComponent,
28
+ ClassComponent,
29
+ HostRoot,
30
+ HostComponent,
31
+ HostText,
32
+ HostPortal,
33
+ ForwardRef,
34
+ Fragment,
35
+ Mode,
36
+ ContextProvider,
37
+ ContextConsumer,
38
+ Profiler,
39
+ SuspenseComponent,
40
+ SuspenseListComponent,
41
+ MemoComponent,
42
+ SimpleMemoComponent,
43
+ LazyComponent,
44
+ IncompleteClassComponent,
45
+ FundamentalComponent,
46
+ ScopeComponent,
47
+ Block,
48
+} from './ReactWorkTags';
49
+import {
50
+ NoEffect,
51
+ PerformedWork,
52
+ Placement,
53
+ Hydrating,
54
+ ContentReset,
55
+ DidCapture,
56
+ Update,
57
+ Ref,
58
+ Deletion,
59
+} from './ReactSideEffectTags';
60
+import ReactSharedInternals from 'shared/ReactSharedInternals';
61
+import {
62
+ debugRenderPhaseSideEffectsForStrictMode,
63
+ disableLegacyContext,
64
+ disableModulePatternComponents,
65
+ enableProfilerTimer,
66
+ enableSchedulerTracing,
67
+ enableSuspenseServerRenderer,
68
+ enableFundamentalAPI,
69
+ warnAboutDefaultPropsOnFunctionComponents,
70
+ enableScopeAPI,
71
+ enableBlocksAPI,
72
+} from 'shared/ReactFeatureFlags';
73
+import invariant from 'shared/invariant';
74
+import shallowEqual from 'shared/shallowEqual';
75
+import getComponentName from 'shared/getComponentName';
76
+import ReactStrictModeWarnings from './ReactStrictModeWarnings.new';
77
+import {REACT_LAZY_TYPE, getIteratorFn} from 'shared/ReactSymbols';
78
+import {
79
+ getCurrentFiberOwnerNameInDevOrNull,
80
+ setIsRendering,
81
+} from './ReactCurrentFiber';
82
+import {
83
+ resolveFunctionForHotReloading,
84
+ resolveForwardRefForHotReloading,
85
+ resolveClassForHotReloading,
86
+} from './ReactFiberHotReloading.new';
87
+
88
+import {
89
+ mountChildFibers,
90
+ reconcileChildFibers,
91
+ cloneChildFibers,
92
+} from './ReactChildFiber.new';
93
+import {
94
+ processUpdateQueue,
95
+ cloneUpdateQueue,
96
+ initializeUpdateQueue,
97
+} from './ReactUpdateQueue.new';
98
+import {
99
+ NoWork,
100
+ Never,
101
+ Sync,
102
+ computeAsyncExpiration,
103
+} from './ReactFiberExpirationTime';
104
+import {
105
+ ConcurrentMode,
106
+ NoMode,
107
+ ProfileMode,
108
+ StrictMode,
109
+ BlockingMode,
110
+} from './ReactTypeOfMode';
111
+import {
112
+ shouldSetTextContent,
113
+ shouldDeprioritizeSubtree,
114
+ isSuspenseInstancePending,
115
+ isSuspenseInstanceFallback,
116
+ registerSuspenseInstanceRetry,
117
+} from './ReactFiberHostConfig';
118
+import type {SuspenseInstance} from './ReactFiberHostConfig';
119
+import {shouldSuspend} from './ReactFiberReconciler';
120
+import {pushHostContext, pushHostContainer} from './ReactFiberHostContext.new';
121
+import {
122
+ suspenseStackCursor,
123
+ pushSuspenseContext,
124
+ InvisibleParentSuspenseContext,
125
+ ForceSuspenseFallback,
126
+ hasSuspenseContext,
127
+ setDefaultShallowSuspenseContext,
128
+ addSubtreeSuspenseContext,
129
+ setShallowSuspenseContext,
130
+} from './ReactFiberSuspenseContext.new';
131
+import {findFirstSuspended} from './ReactFiberSuspenseComponent.new';
132
+import {
133
+ pushProvider,
134
+ propagateContextChange,
135
+ readContext,
136
+ prepareToReadContext,
137
+ calculateChangedBits,
138
+ scheduleWorkOnParentPath,
139
+} from './ReactFiberNewContext.new';
140
+import {renderWithHooks, bailoutHooks} from './ReactFiberHooks.new';
141
+import {stopProfilerTimerIfRunning} from './ReactProfilerTimer.new';
142
+import {
143
+ getMaskedContext,
144
+ getUnmaskedContext,
145
+ hasContextChanged as hasLegacyContextChanged,
146
+ pushContextProvider as pushLegacyContextProvider,
147
+ isContextProvider as isLegacyContextProvider,
148
+ pushTopLevelContextObject,
149
+ invalidateContextProvider,
150
+} from './ReactFiberContext.new';
151
+import {
152
+ enterHydrationState,
153
+ reenterHydrationStateFromDehydratedSuspenseInstance,
154
+ resetHydrationState,
155
+ tryToClaimNextHydratableInstance,
156
+ warnIfHydrating,
157
+} from './ReactFiberHydrationContext.new';
158
+import {
159
+ adoptClassInstance,
160
+ applyDerivedStateFromProps,
161
+ constructClassInstance,
162
+ mountClassInstance,
163
+ resumeMountClassInstance,
164
+ updateClassInstance,
165
+} from './ReactFiberClassComponent.new';
166
+import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
167
+import {
168
+ resolveLazyComponentTag,
169
+ createFiberFromTypeAndProps,
170
+ createFiberFromFragment,
171
+ createWorkInProgress,
172
+ isSimpleFunctionComponent,
173
+} from './ReactFiber.new';
174
+import {
175
+ markSpawnedWork,
176
+ requestCurrentTimeForUpdate,
177
+ retryDehydratedSuspenseBoundary,
178
+ scheduleUpdateOnFiber,
179
+ renderDidSuspendDelayIfPossible,
180
+ markUnprocessedUpdateTime,
181
+ getWorkInProgressRoot,
182
+} from './ReactFiberWorkLoop.new';
183
+
184
+import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
185
+
186
+const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
187
+
188
+let didReceiveUpdate: boolean = false;
189
+
190
+let didWarnAboutBadClass;
191
+let didWarnAboutModulePatternComponent;
192
+let didWarnAboutContextTypeOnFunctionComponent;
193
+let didWarnAboutGetDerivedStateOnFunctionComponent;
194
+let didWarnAboutFunctionRefs;
195
+export let didWarnAboutReassigningProps;
196
+let didWarnAboutRevealOrder;
197
+let didWarnAboutTailOptions;
198
+let didWarnAboutDefaultPropsOnFunctionComponent;
199
+
200
+if (__DEV__) {
201
+ didWarnAboutBadClass = {};
202
+ didWarnAboutModulePatternComponent = {};
203
+ didWarnAboutContextTypeOnFunctionComponent = {};
204
+ didWarnAboutGetDerivedStateOnFunctionComponent = {};
205
+ didWarnAboutFunctionRefs = {};
206
+ didWarnAboutReassigningProps = false;
207
+ didWarnAboutRevealOrder = {};
208
+ didWarnAboutTailOptions = {};
209
+ didWarnAboutDefaultPropsOnFunctionComponent = {};
210
+}
211
+
212
+export function reconcileChildren(
213
+ current: Fiber | null,
214
+ workInProgress: Fiber,
215
+ nextChildren: any,
216
+ renderExpirationTime: ExpirationTime,
217
+) {
218
+ if (current === null) {
219
+ // If this is a fresh new component that hasn't been rendered yet, we
220
+ // won't update its child set by applying minimal side-effects. Instead,
221
+ // we will add them all to the child before it gets rendered. That means
222
+ // we can optimize this reconciliation pass by not tracking side-effects.
223
+ workInProgress.child = mountChildFibers(
224
+ workInProgress,
225
+ null,
226
+ nextChildren,
227
+ renderExpirationTime,
228
+ );
229
+ } else {
230
+ // If the current child is the same as the work in progress, it means that
231
+ // we haven't yet started any work on these children. Therefore, we use
232
+ // the clone algorithm to create a copy of all the current children.
233
+
234
+ // If we had any progressed work already, that is invalid at this point so
235
+ // let's throw it out.
236
+ workInProgress.child = reconcileChildFibers(
237
+ workInProgress,
238
+ current.child,
239
+ nextChildren,
240
+ renderExpirationTime,
241
+ );
242
+ }
243
+}
244
+
245
+function forceUnmountCurrentAndReconcile(
246
+ current: Fiber,
247
+ workInProgress: Fiber,
248
+ nextChildren: any,
249
+ renderExpirationTime: ExpirationTime,
250
+) {
251
+ // This function is fork of reconcileChildren. It's used in cases where we
252
+ // want to reconcile without matching against the existing set. This has the
253
+ // effect of all current children being unmounted; even if the type and key
254
+ // are the same, the old child is unmounted and a new child is created.
255
+ //
256
+ // To do this, we're going to go through the reconcile algorithm twice. In
257
+ // the first pass, we schedule a deletion for all the current children by
258
+ // passing null.
259
+ workInProgress.child = reconcileChildFibers(
260
+ workInProgress,
261
+ current.child,
262
+ null,
263
+ renderExpirationTime,
264
+ );
265
+ // In the second pass, we mount the new children. The trick here is that we
266
+ // pass null in place of where we usually pass the current child set. This has
267
+ // the effect of remounting all children regardless of whether their
268
+ // identities match.
269
+ workInProgress.child = reconcileChildFibers(
270
+ workInProgress,
271
+ null,
272
+ nextChildren,
273
+ renderExpirationTime,
274
+ );
275
+}
276
+
277
+function updateForwardRef(
278
+ current: Fiber | null,
279
+ workInProgress: Fiber,
280
+ Component: any,
281
+ nextProps: any,
282
+ renderExpirationTime: ExpirationTime,
283
+) {
284
+ // TODO: current can be non-null here even if the component
285
+ // hasn't yet mounted. This happens after the first render suspends.
286
+ // We'll need to figure out if this is fine or can cause issues.
287
+
288
+ if (__DEV__) {
289
+ if (workInProgress.type !== workInProgress.elementType) {
290
+ // Lazy component props can't be validated in createElement
291
+ // because they're only guaranteed to be resolved here.
292
+ const innerPropTypes = Component.propTypes;
293
+ if (innerPropTypes) {
294
+ checkPropTypes(
295
+ innerPropTypes,
296
+ nextProps, // Resolved props
297
+ 'prop',
298
+ getComponentName(Component),
299
+ );
300
+ }
301
+ }
302
+ }
303
+
304
+ const render = Component.render;
305
+ const ref = workInProgress.ref;
306
+
307
+ // The rest is a fork of updateFunctionComponent
308
+ let nextChildren;
309
+ prepareToReadContext(workInProgress, renderExpirationTime);
310
+ if (__DEV__) {
311
+ ReactCurrentOwner.current = workInProgress;
312
+ setIsRendering(true);
313
+ nextChildren = renderWithHooks(
314
+ current,
315
+ workInProgress,
316
+ render,
317
+ nextProps,
318
+ ref,
319
+ renderExpirationTime,
320
+ );
321
+ if (
322
+ debugRenderPhaseSideEffectsForStrictMode &&
323
+ workInProgress.mode & StrictMode
324
+ ) {
325
+ disableLogs();
326
+ try {
327
+ nextChildren = renderWithHooks(
328
+ current,
329
+ workInProgress,
330
+ render,
331
+ nextProps,
332
+ ref,
333
+ renderExpirationTime,
334
+ );
335
+ } finally {
336
+ reenableLogs();
337
+ }
338
+ }
339
+ setIsRendering(false);
340
+ } else {
341
+ nextChildren = renderWithHooks(
342
+ current,
343
+ workInProgress,
344
+ render,
345
+ nextProps,
346
+ ref,
347
+ renderExpirationTime,
348
+ );
349
+ }
350
+
351
+ if (current !== null && !didReceiveUpdate) {
352
+ bailoutHooks(current, workInProgress, renderExpirationTime);
353
+ return bailoutOnAlreadyFinishedWork(
354
+ current,
355
+ workInProgress,
356
+ renderExpirationTime,
357
+ );
358
+ }
359
+
360
+ // React DevTools reads this flag.
361
+ workInProgress.effectTag |= PerformedWork;
362
+ reconcileChildren(
363
+ current,
364
+ workInProgress,
365
+ nextChildren,
366
+ renderExpirationTime,
367
+ );
368
+ return workInProgress.child;
369
+}
370
+
371
+function updateMemoComponent(
372
+ current: Fiber | null,
373
+ workInProgress: Fiber,
374
+ Component: any,
375
+ nextProps: any,
376
+ updateExpirationTime,
377
+ renderExpirationTime: ExpirationTime,
378
+): null | Fiber {
379
+ if (current === null) {
380
+ const type = Component.type;
381
+ if (
382
+ isSimpleFunctionComponent(type) &&
383
+ Component.compare === null &&
384
+ // SimpleMemoComponent codepath doesn't resolve outer props either.
385
+ Component.defaultProps === undefined
386
+ ) {
387
+ let resolvedType = type;
388
+ if (__DEV__) {
389
+ resolvedType = resolveFunctionForHotReloading(type);
390
+ }
391
+ // If this is a plain function component without default props,
392
+ // and with only the default shallow comparison, we upgrade it
393
+ // to a SimpleMemoComponent to allow fast path updates.
394
+ workInProgress.tag = SimpleMemoComponent;
395
+ workInProgress.type = resolvedType;
396
+ if (__DEV__) {
397
+ validateFunctionComponentInDev(workInProgress, type);
398
+ }
399
+ return updateSimpleMemoComponent(
400
+ current,
401
+ workInProgress,
402
+ resolvedType,
403
+ nextProps,
404
+ updateExpirationTime,
405
+ renderExpirationTime,
406
+ );
407
+ }
408
+ if (__DEV__) {
409
+ const innerPropTypes = type.propTypes;
410
+ if (innerPropTypes) {
411
+ // Inner memo component props aren't currently validated in createElement.
412
+ // We could move it there, but we'd still need this for lazy code path.
413
+ checkPropTypes(
414
+ innerPropTypes,
415
+ nextProps, // Resolved props
416
+ 'prop',
417
+ getComponentName(type),
418
+ );
419
+ }
420
+ }
421
+ const child = createFiberFromTypeAndProps(
422
+ Component.type,
423
+ null,
424
+ nextProps,
425
+ null,
426
+ workInProgress.mode,
427
+ renderExpirationTime,
428
+ );
429
+ child.ref = workInProgress.ref;
430
+ child.return = workInProgress;
431
+ workInProgress.child = child;
432
+ return child;
433
+ }
434
+ if (__DEV__) {
435
+ const type = Component.type;
436
+ const innerPropTypes = type.propTypes;
437
+ if (innerPropTypes) {
438
+ // Inner memo component props aren't currently validated in createElement.
439
+ // We could move it there, but we'd still need this for lazy code path.
440
+ checkPropTypes(
441
+ innerPropTypes,
442
+ nextProps, // Resolved props
443
+ 'prop',
444
+ getComponentName(type),
445
+ );
446
+ }
447
+ }
448
+ const currentChild = ((current.child: any): Fiber); // This is always exactly one child
449
+ if (updateExpirationTime < renderExpirationTime) {
450
+ // This will be the props with resolved defaultProps,
451
+ // unlike current.memoizedProps which will be the unresolved ones.
452
+ const prevProps = currentChild.memoizedProps;
453
+ // Default to shallow comparison
454
+ let compare = Component.compare;
455
+ compare = compare !== null ? compare : shallowEqual;
456
+ if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
457
+ return bailoutOnAlreadyFinishedWork(
458
+ current,
459
+ workInProgress,
460
+ renderExpirationTime,
461
+ );
462
+ }
463
+ }
464
+ // React DevTools reads this flag.
465
+ workInProgress.effectTag |= PerformedWork;
466
+ const newChild = createWorkInProgress(currentChild, nextProps);
467
+ newChild.ref = workInProgress.ref;
468
+ newChild.return = workInProgress;
469
+ workInProgress.child = newChild;
470
+ return newChild;
471
+}
472
+
473
+function updateSimpleMemoComponent(
474
+ current: Fiber | null,
475
+ workInProgress: Fiber,
476
+ Component: any,
477
+ nextProps: any,
478
+ updateExpirationTime,
479
+ renderExpirationTime: ExpirationTime,
480
+): null | Fiber {
481
+ // TODO: current can be non-null here even if the component
482
+ // hasn't yet mounted. This happens when the inner render suspends.
483
+ // We'll need to figure out if this is fine or can cause issues.
484
+
485
+ if (__DEV__) {
486
+ if (workInProgress.type !== workInProgress.elementType) {
487
+ // Lazy component props can't be validated in createElement
488
+ // because they're only guaranteed to be resolved here.
489
+ let outerMemoType = workInProgress.elementType;
490
+ if (outerMemoType.$$typeof === REACT_LAZY_TYPE) {
491
+ // We warn when you define propTypes on lazy()
492
+ // so let's just skip over it to find memo() outer wrapper.
493
+ // Inner props for memo are validated later.
494
+ const lazyComponent: LazyComponentType<any, any> = outerMemoType;
495
+ const payload = lazyComponent._payload;
496
+ const init = lazyComponent._init;
497
+ try {
498
+ outerMemoType = init(payload);
499
+ } catch (x) {
500
+ outerMemoType = null;
501
+ }
502
+ // Inner propTypes will be validated in the function component path.
503
+ const outerPropTypes = outerMemoType && (outerMemoType: any).propTypes;
504
+ if (outerPropTypes) {
505
+ checkPropTypes(
506
+ outerPropTypes,
507
+ nextProps, // Resolved (SimpleMemoComponent has no defaultProps)
508
+ 'prop',
509
+ getComponentName(outerMemoType),
510
+ );
511
+ }
512
+ }
513
+ }
514
+ }
515
+ if (current !== null) {
516
+ const prevProps = current.memoizedProps;
517
+ if (
518
+ shallowEqual(prevProps, nextProps) &&
519
+ current.ref === workInProgress.ref &&
520
+ // Prevent bailout if the implementation changed due to hot reload.
521
+ (__DEV__ ? workInProgress.type === current.type : true)
522
+ ) {
523
+ didReceiveUpdate = false;
524
+ if (updateExpirationTime < renderExpirationTime) {
525
+ // The pending update priority was cleared at the beginning of
526
+ // beginWork. We're about to bail out, but there might be additional
527
+ // updates at a lower priority. Usually, the priority level of the
528
+ // remaining updates is accumlated during the evaluation of the
529
+ // component (i.e. when processing the update queue). But since since
530
+ // we're bailing out early *without* evaluating the component, we need
531
+ // to account for it here, too. Reset to the value of the current fiber.
532
+ // NOTE: This only applies to SimpleMemoComponent, not MemoComponent,
533
+ // because a MemoComponent fiber does not have hooks or an update queue;
534
+ // rather, it wraps around an inner component, which may or may not
535
+ // contains hooks.
536
+ // TODO: Move the reset at in beginWork out of the common path so that
537
+ // this is no longer necessary.
538
+ workInProgress.expirationTime = current.expirationTime;
539
+ return bailoutOnAlreadyFinishedWork(
540
+ current,
541
+ workInProgress,
542
+ renderExpirationTime,
543
+ );
544
+ }
545
+ }
546
+ }
547
+ return updateFunctionComponent(
548
+ current,
549
+ workInProgress,
550
+ Component,
551
+ nextProps,
552
+ renderExpirationTime,
553
+ );
554
+}
555
+
556
+function updateFragment(
557
+ current: Fiber | null,
558
+ workInProgress: Fiber,
559
+ renderExpirationTime: ExpirationTime,
560
+) {
561
+ const nextChildren = workInProgress.pendingProps;
562
+ reconcileChildren(
563
+ current,
564
+ workInProgress,
565
+ nextChildren,
566
+ renderExpirationTime,
567
+ );
568
+ return workInProgress.child;
569
+}
570
+
571
+function updateMode(
572
+ current: Fiber | null,
573
+ workInProgress: Fiber,
574
+ renderExpirationTime: ExpirationTime,
575
+) {
576
+ const nextChildren = workInProgress.pendingProps.children;
577
+ reconcileChildren(
578
+ current,
579
+ workInProgress,
580
+ nextChildren,
581
+ renderExpirationTime,
582
+ );
583
+ return workInProgress.child;
584
+}
585
+
586
+function updateProfiler(
587
+ current: Fiber | null,
588
+ workInProgress: Fiber,
589
+ renderExpirationTime: ExpirationTime,
590
+) {
591
+ if (enableProfilerTimer) {
592
+ workInProgress.effectTag |= Update;
593
+
594
+ // Reset effect durations for the next eventual effect phase.
595
+ // These are reset during render to allow the DevTools commit hook a chance to read them,
596
+ const stateNode = workInProgress.stateNode;
597
+ stateNode.effectDuration = 0;
598
+ stateNode.passiveEffectDuration = 0;
599
+ }
600
+ const nextProps = workInProgress.pendingProps;
601
+ const nextChildren = nextProps.children;
602
+ reconcileChildren(
603
+ current,
604
+ workInProgress,
605
+ nextChildren,
606
+ renderExpirationTime,
607
+ );
608
+ return workInProgress.child;
609
+}
610
+
611
+function markRef(current: Fiber | null, workInProgress: Fiber) {
612
+ const ref = workInProgress.ref;
613
+ if (
614
+ (current === null && ref !== null) ||
615
+ (current !== null && current.ref !== ref)
616
+ ) {
617
+ // Schedule a Ref effect
618
+ workInProgress.effectTag |= Ref;
619
+ }
620
+}
621
+
622
+function updateFunctionComponent(
623
+ current,
624
+ workInProgress,
625
+ Component,
626
+ nextProps: any,
627
+ renderExpirationTime,
628
+) {
629
+ if (__DEV__) {
630
+ if (workInProgress.type !== workInProgress.elementType) {
631
+ // Lazy component props can't be validated in createElement
632
+ // because they're only guaranteed to be resolved here.
633
+ const innerPropTypes = Component.propTypes;
634
+ if (innerPropTypes) {
635
+ checkPropTypes(
636
+ innerPropTypes,
637
+ nextProps, // Resolved props
638
+ 'prop',
639
+ getComponentName(Component),
640
+ );
641
+ }
642
+ }
643
+ }
644
+
645
+ let context;
646
+ if (!disableLegacyContext) {
647
+ const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
648
+ context = getMaskedContext(workInProgress, unmaskedContext);
649
+ }
650
+
651
+ let nextChildren;
652
+ prepareToReadContext(workInProgress, renderExpirationTime);
653
+ if (__DEV__) {
654
+ ReactCurrentOwner.current = workInProgress;
655
+ setIsRendering(true);
656
+ nextChildren = renderWithHooks(
657
+ current,
658
+ workInProgress,
659
+ Component,
660
+ nextProps,
661
+ context,
662
+ renderExpirationTime,
663
+ );
664
+ if (
665
+ debugRenderPhaseSideEffectsForStrictMode &&
666
+ workInProgress.mode & StrictMode
667
+ ) {
668
+ disableLogs();
669
+ try {
670
+ nextChildren = renderWithHooks(
671
+ current,
672
+ workInProgress,
673
+ Component,
674
+ nextProps,
675
+ context,
676
+ renderExpirationTime,
677
+ );
678
+ } finally {
679
+ reenableLogs();
680
+ }
681
+ }
682
+ setIsRendering(false);
683
+ } else {
684
+ nextChildren = renderWithHooks(
685
+ current,
686
+ workInProgress,
687
+ Component,
688
+ nextProps,
689
+ context,
690
+ renderExpirationTime,
691
+ );
692
+ }
693
+
694
+ if (current !== null && !didReceiveUpdate) {
695
+ bailoutHooks(current, workInProgress, renderExpirationTime);
696
+ return bailoutOnAlreadyFinishedWork(
697
+ current,
698
+ workInProgress,
699
+ renderExpirationTime,
700
+ );
701
+ }
702
+
703
+ // React DevTools reads this flag.
704
+ workInProgress.effectTag |= PerformedWork;
705
+ reconcileChildren(
706
+ current,
707
+ workInProgress,
708
+ nextChildren,
709
+ renderExpirationTime,
710
+ );
711
+ return workInProgress.child;
712
+}
713
+
714
+function updateBlock<Props, Data>(
715
+ current: Fiber | null,
716
+ workInProgress: Fiber,
717
+ block: BlockComponent<Props, Data>,
718
+ nextProps: any,
719
+ renderExpirationTime: ExpirationTime,
720
+) {
721
+ // TODO: current can be non-null here even if the component
722
+ // hasn't yet mounted. This happens after the first render suspends.
723
+ // We'll need to figure out if this is fine or can cause issues.
724
+
725
+ const render = block._render;
726
+ const data = block._data;
727
+
728
+ // The rest is a fork of updateFunctionComponent
729
+ let nextChildren;
730
+ prepareToReadContext(workInProgress, renderExpirationTime);
731
+ if (__DEV__) {
732
+ ReactCurrentOwner.current = workInProgress;
733
+ setIsRendering(true);
734
+ nextChildren = renderWithHooks(
735
+ current,
736
+ workInProgress,
737
+ render,
738
+ nextProps,
739
+ data,
740
+ renderExpirationTime,
741
+ );
742
+ if (
743
+ debugRenderPhaseSideEffectsForStrictMode &&
744
+ workInProgress.mode & StrictMode
745
+ ) {
746
+ disableLogs();
747
+ try {
748
+ nextChildren = renderWithHooks(
749
+ current,
750
+ workInProgress,
751
+ render,
752
+ nextProps,
753
+ data,
754
+ renderExpirationTime,
755
+ );
756
+ } finally {
757
+ reenableLogs();
758
+ }
759
+ }
760
+ setIsRendering(false);
761
+ } else {
762
+ nextChildren = renderWithHooks(
763
+ current,
764
+ workInProgress,
765
+ render,
766
+ nextProps,
767
+ data,
768
+ renderExpirationTime,
769
+ );
770
+ }
771
+
772
+ if (current !== null && !didReceiveUpdate) {
773
+ bailoutHooks(current, workInProgress, renderExpirationTime);
774
+ return bailoutOnAlreadyFinishedWork(
775
+ current,
776
+ workInProgress,
777
+ renderExpirationTime,
778
+ );
779
+ }
780
+
781
+ // React DevTools reads this flag.
782
+ workInProgress.effectTag |= PerformedWork;
783
+ reconcileChildren(
784
+ current,
785
+ workInProgress,
786
+ nextChildren,
787
+ renderExpirationTime,
788
+ );
789
+ return workInProgress.child;
790
+}
791
+
792
+function updateClassComponent(
793
+ current: Fiber | null,
794
+ workInProgress: Fiber,
795
+ Component: any,
796
+ nextProps,
797
+ renderExpirationTime: ExpirationTime,
798
+) {
799
+ if (__DEV__) {
800
+ if (workInProgress.type !== workInProgress.elementType) {
801
+ // Lazy component props can't be validated in createElement
802
+ // because they're only guaranteed to be resolved here.
803
+ const innerPropTypes = Component.propTypes;
804
+ if (innerPropTypes) {
805
+ checkPropTypes(
806
+ innerPropTypes,
807
+ nextProps, // Resolved props
808
+ 'prop',
809
+ getComponentName(Component),
810
+ );
811
+ }
812
+ }
813
+ }
814
+
815
+ // Push context providers early to prevent context stack mismatches.
816
+ // During mounting we don't know the child context yet as the instance doesn't exist.
817
+ // We will invalidate the child context in finishClassComponent() right after rendering.
818
+ let hasContext;
819
+ if (isLegacyContextProvider(Component)) {
820
+ hasContext = true;
821
+ pushLegacyContextProvider(workInProgress);
822
+ } else {
823
+ hasContext = false;
824
+ }
825
+ prepareToReadContext(workInProgress, renderExpirationTime);
826
+
827
+ const instance = workInProgress.stateNode;
828
+ let shouldUpdate;
829
+ if (instance === null) {
830
+ if (current !== null) {
831
+ // A class component without an instance only mounts if it suspended
832
+ // inside a non-concurrent tree, in an inconsistent state. We want to
833
+ // treat it like a new mount, even though an empty version of it already
834
+ // committed. Disconnect the alternate pointers.
835
+ current.alternate = null;
836
+ workInProgress.alternate = null;
837
+ // Since this is conceptually a new fiber, schedule a Placement effect
838
+ workInProgress.effectTag |= Placement;
839
+ }
840
+ // In the initial pass we might need to construct the instance.
841
+ constructClassInstance(workInProgress, Component, nextProps);
842
+ mountClassInstance(
843
+ workInProgress,
844
+ Component,
845
+ nextProps,
846
+ renderExpirationTime,
847
+ );
848
+ shouldUpdate = true;
849
+ } else if (current === null) {
850
+ // In a resume, we'll already have an instance we can reuse.
851
+ shouldUpdate = resumeMountClassInstance(
852
+ workInProgress,
853
+ Component,
854
+ nextProps,
855
+ renderExpirationTime,
856
+ );
857
+ } else {
858
+ shouldUpdate = updateClassInstance(
859
+ current,
860
+ workInProgress,
861
+ Component,
862
+ nextProps,
863
+ renderExpirationTime,
864
+ );
865
+ }
866
+ const nextUnitOfWork = finishClassComponent(
867
+ current,
868
+ workInProgress,
869
+ Component,
870
+ shouldUpdate,
871
+ hasContext,
872
+ renderExpirationTime,
873
+ );
874
+ if (__DEV__) {
875
+ const inst = workInProgress.stateNode;
876
+ if (shouldUpdate && inst.props !== nextProps) {
877
+ if (!didWarnAboutReassigningProps) {
878
+ console.error(
879
+ 'It looks like %s is reassigning its own `this.props` while rendering. ' +
880
+ 'This is not supported and can lead to confusing bugs.',
881
+ getComponentName(workInProgress.type) || 'a component',
882
+ );
883
+ }
884
+ didWarnAboutReassigningProps = true;
885
+ }
886
+ }
887
+ return nextUnitOfWork;
888
+}
889
+
890
+function finishClassComponent(
891
+ current: Fiber | null,
892
+ workInProgress: Fiber,
893
+ Component: any,
894
+ shouldUpdate: boolean,
895
+ hasContext: boolean,
896
+ renderExpirationTime: ExpirationTime,
897
+) {
898
+ // Refs should update even if shouldComponentUpdate returns false
899
+ markRef(current, workInProgress);
900
+
901
+ const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;
902
+
903
+ if (!shouldUpdate && !didCaptureError) {
904
+ // Context providers should defer to sCU for rendering
905
+ if (hasContext) {
906
+ invalidateContextProvider(workInProgress, Component, false);
907
+ }
908
+
909
+ return bailoutOnAlreadyFinishedWork(
910
+ current,
911
+ workInProgress,
912
+ renderExpirationTime,
913
+ );
914
+ }
915
+
916
+ const instance = workInProgress.stateNode;
917
+
918
+ // Rerender
919
+ ReactCurrentOwner.current = workInProgress;
920
+ let nextChildren;
921
+ if (
922
+ didCaptureError &&
923
+ typeof Component.getDerivedStateFromError !== 'function'
924
+ ) {
925
+ // If we captured an error, but getDerivedStateFromError is not defined,
926
+ // unmount all the children. componentDidCatch will schedule an update to
927
+ // re-render a fallback. This is temporary until we migrate everyone to
928
+ // the new API.
929
+ // TODO: Warn in a future release.
930
+ nextChildren = null;
931
+
932
+ if (enableProfilerTimer) {
933
+ stopProfilerTimerIfRunning(workInProgress);
934
+ }
935
+ } else {
936
+ if (__DEV__) {
937
+ setIsRendering(true);
938
+ nextChildren = instance.render();
939
+ if (
940
+ debugRenderPhaseSideEffectsForStrictMode &&
941
+ workInProgress.mode & StrictMode
942
+ ) {
943
+ disableLogs();
944
+ try {
945
+ instance.render();
946
+ } finally {
947
+ reenableLogs();
948
+ }
949
+ }
950
+ setIsRendering(false);
951
+ } else {
952
+ nextChildren = instance.render();
953
+ }
954
+ }
955
+
956
+ // React DevTools reads this flag.
957
+ workInProgress.effectTag |= PerformedWork;
958
+ if (current !== null && didCaptureError) {
959
+ // If we're recovering from an error, reconcile without reusing any of
960
+ // the existing children. Conceptually, the normal children and the children
961
+ // that are shown on error are two different sets, so we shouldn't reuse
962
+ // normal children even if their identities match.
963
+ forceUnmountCurrentAndReconcile(
964
+ current,
965
+ workInProgress,
966
+ nextChildren,
967
+ renderExpirationTime,
968
+ );
969
+ } else {
970
+ reconcileChildren(
971
+ current,
972
+ workInProgress,
973
+ nextChildren,
974
+ renderExpirationTime,
975
+ );
976
+ }
977
+
978
+ // Memoize state using the values we just used to render.
979
+ // TODO: Restructure so we never read values from the instance.
980
+ workInProgress.memoizedState = instance.state;
981
+
982
+ // The context might have changed so we need to recalculate it.
983
+ if (hasContext) {
984
+ invalidateContextProvider(workInProgress, Component, true);
985
+ }
986
+
987
+ return workInProgress.child;
988
+}
989
+
990
+function pushHostRootContext(workInProgress) {
991
+ const root = (workInProgress.stateNode: FiberRoot);
992
+ if (root.pendingContext) {
993
+ pushTopLevelContextObject(
994
+ workInProgress,
995
+ root.pendingContext,
996
+ root.pendingContext !== root.context,
997
+ );
998
+ } else if (root.context) {
999
+ // Should always be set
1000
+ pushTopLevelContextObject(workInProgress, root.context, false);
1001
+ }
1002
+ pushHostContainer(workInProgress, root.containerInfo);
1003
+}
1004
+
1005
+function updateHostRoot(current, workInProgress, renderExpirationTime) {
1006
+ pushHostRootContext(workInProgress);
1007
+ const updateQueue = workInProgress.updateQueue;
1008
+ invariant(
1009
+ current !== null && updateQueue !== null,
1010
+ 'If the root does not have an updateQueue, we should have already ' +
1011
+ 'bailed out. This error is likely caused by a bug in React. Please ' +
1012
+ 'file an issue.',
1013
+ );
1014
+ const nextProps = workInProgress.pendingProps;
1015
+ const prevState = workInProgress.memoizedState;
1016
+ const prevChildren = prevState !== null ? prevState.element : null;
1017
+ cloneUpdateQueue(current, workInProgress);
1018
+ processUpdateQueue(workInProgress, nextProps, null, renderExpirationTime);
1019
+ const nextState = workInProgress.memoizedState;
1020
+ // Caution: React DevTools currently depends on this property
1021
+ // being called "element".
1022
+ const nextChildren = nextState.element;
1023
+ if (nextChildren === prevChildren) {
1024
+ // If the state is the same as before, that's a bailout because we had
1025
+ // no work that expires at this time.
1026
+ resetHydrationState();
1027
+ return bailoutOnAlreadyFinishedWork(
1028
+ current,
1029
+ workInProgress,
1030
+ renderExpirationTime,
1031
+ );
1032
+ }
1033
+ const root: FiberRoot = workInProgress.stateNode;
1034
+ if (root.hydrate && enterHydrationState(workInProgress)) {
1035
+ // If we don't have any current children this might be the first pass.
1036
+ // We always try to hydrate. If this isn't a hydration pass there won't
1037
+ // be any children to hydrate which is effectively the same thing as
1038
+ // not hydrating.
1039
+
1040
+ const child = mountChildFibers(
1041
+ workInProgress,
1042
+ null,
1043
+ nextChildren,
1044
+ renderExpirationTime,
1045
+ );
1046
+ workInProgress.child = child;
1047
+
1048
+ let node = child;
1049
+ while (node) {
1050
+ // Mark each child as hydrating. This is a fast path to know whether this
1051
+ // tree is part of a hydrating tree. This is used to determine if a child
1052
+ // node has fully mounted yet, and for scheduling event replaying.
1053
+ // Conceptually this is similar to Placement in that a new subtree is
1054
+ // inserted into the React tree here. It just happens to not need DOM
1055
+ // mutations because it already exists.
1056
+ node.effectTag = (node.effectTag & ~Placement) | Hydrating;
1057
+ node = node.sibling;
1058
+ }
1059
+ } else {
1060
+ // Otherwise reset hydration state in case we aborted and resumed another
1061
+ // root.
1062
+ reconcileChildren(
1063
+ current,
1064
+ workInProgress,
1065
+ nextChildren,
1066
+ renderExpirationTime,
1067
+ );
1068
+ resetHydrationState();
1069
+ }
1070
+ return workInProgress.child;
1071
+}
1072
+
1073
+function updateHostComponent(current, workInProgress, renderExpirationTime) {
1074
+ pushHostContext(workInProgress);
1075
+
1076
+ if (current === null) {
1077
+ tryToClaimNextHydratableInstance(workInProgress);
1078
+ }
1079
+
1080
+ const type = workInProgress.type;
1081
+ const nextProps = workInProgress.pendingProps;
1082
+ const prevProps = current !== null ? current.memoizedProps : null;
1083
+
1084
+ let nextChildren = nextProps.children;
1085
+ const isDirectTextChild = shouldSetTextContent(type, nextProps);
1086
+
1087
+ if (isDirectTextChild) {
1088
+ // We special case a direct text child of a host node. This is a common
1089
+ // case. We won't handle it as a reified child. We will instead handle
1090
+ // this in the host environment that also has access to this prop. That
1091
+ // avoids allocating another HostText fiber and traversing it.
1092
+ nextChildren = null;
1093
+ } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
1094
+ // If we're switching from a direct text child to a normal child, or to
1095
+ // empty, we need to schedule the text content to be reset.
1096
+ workInProgress.effectTag |= ContentReset;
1097
+ }
1098
+
1099
+ markRef(current, workInProgress);
1100
+
1101
+ // Check the host config to see if the children are offscreen/hidden.
1102
+ if (
1103
+ workInProgress.mode & ConcurrentMode &&
1104
+ renderExpirationTime !== Never &&
1105
+ shouldDeprioritizeSubtree(type, nextProps)
1106
+ ) {
1107
+ if (enableSchedulerTracing) {
1108
+ markSpawnedWork(Never);
1109
+ }
1110
+ // Schedule this fiber to re-render at offscreen priority. Then bailout.
1111
+ workInProgress.expirationTime = workInProgress.childExpirationTime = Never;
1112
+ return null;
1113
+ }
1114
+
1115
+ reconcileChildren(
1116
+ current,
1117
+ workInProgress,
1118
+ nextChildren,
1119
+ renderExpirationTime,
1120
+ );
1121
+ return workInProgress.child;
1122
+}
1123
+
1124
+function updateHostText(current, workInProgress) {
1125
+ if (current === null) {
1126
+ tryToClaimNextHydratableInstance(workInProgress);
1127
+ }
1128
+ // Nothing to do here. This is terminal. We'll do the completion step
1129
+ // immediately after.
1130
+ return null;
1131
+}
1132
+
1133
+function mountLazyComponent(
1134
+ _current,
1135
+ workInProgress,
1136
+ elementType,
1137
+ updateExpirationTime,
1138
+ renderExpirationTime,
1139
+) {
1140
+ if (_current !== null) {
1141
+ // A lazy component only mounts if it suspended inside a non-
1142
+ // concurrent tree, in an inconsistent state. We want to treat it like
1143
+ // a new mount, even though an empty version of it already committed.
1144
+ // Disconnect the alternate pointers.
1145
+ _current.alternate = null;
1146
+ workInProgress.alternate = null;
1147
+ // Since this is conceptually a new fiber, schedule a Placement effect
1148
+ workInProgress.effectTag |= Placement;
1149
+ }
1150
+
1151
+ const props = workInProgress.pendingProps;
1152
+ const lazyComponent: LazyComponentType<any, any> = elementType;
1153
+ const payload = lazyComponent._payload;
1154
+ const init = lazyComponent._init;
1155
+ let Component = init(payload);
1156
+ // Store the unwrapped component in the type.
1157
+ workInProgress.type = Component;
1158
+ const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(Component));
1159
+ const resolvedProps = resolveDefaultProps(Component, props);
1160
+ let child;
1161
+ switch (resolvedTag) {
1162
+ case FunctionComponent: {
1163
+ if (__DEV__) {
1164
+ validateFunctionComponentInDev(workInProgress, Component);
1165
+ workInProgress.type = Component = resolveFunctionForHotReloading(
1166
+ Component,
1167
+ );
1168
+ }
1169
+ child = updateFunctionComponent(
1170
+ null,
1171
+ workInProgress,
1172
+ Component,
1173
+ resolvedProps,
1174
+ renderExpirationTime,
1175
+ );
1176
+ return child;
1177
+ }
1178
+ case ClassComponent: {
1179
+ if (__DEV__) {
1180
+ workInProgress.type = Component = resolveClassForHotReloading(
1181
+ Component,
1182
+ );
1183
+ }
1184
+ child = updateClassComponent(
1185
+ null,
1186
+ workInProgress,
1187
+ Component,
1188
+ resolvedProps,
1189
+ renderExpirationTime,
1190
+ );
1191
+ return child;
1192
+ }
1193
+ case ForwardRef: {
1194
+ if (__DEV__) {
1195
+ workInProgress.type = Component = resolveForwardRefForHotReloading(
1196
+ Component,
1197
+ );
1198
+ }
1199
+ child = updateForwardRef(
1200
+ null,
1201
+ workInProgress,
1202
+ Component,
1203
+ resolvedProps,
1204
+ renderExpirationTime,
1205
+ );
1206
+ return child;
1207
+ }
1208
+ case MemoComponent: {
1209
+ if (__DEV__) {
1210
+ if (workInProgress.type !== workInProgress.elementType) {
1211
+ const outerPropTypes = Component.propTypes;
1212
+ if (outerPropTypes) {
1213
+ checkPropTypes(
1214
+ outerPropTypes,
1215
+ resolvedProps, // Resolved for outer only
1216
+ 'prop',
1217
+ getComponentName(Component),
1218
+ );
1219
+ }
1220
+ }
1221
+ }
1222
+ child = updateMemoComponent(
1223
+ null,
1224
+ workInProgress,
1225
+ Component,
1226
+ resolveDefaultProps(Component.type, resolvedProps), // The inner type can have defaults too
1227
+ updateExpirationTime,
1228
+ renderExpirationTime,
1229
+ );
1230
+ return child;
1231
+ }
1232
+ case Block: {
1233
+ if (enableBlocksAPI) {
1234
+ // TODO: Resolve for Hot Reloading.
1235
+ child = updateBlock(
1236
+ null,
1237
+ workInProgress,
1238
+ Component,
1239
+ props,
1240
+ renderExpirationTime,
1241
+ );
1242
+ return child;
1243
+ }
1244
+ break;
1245
+ }
1246
+ }
1247
+ let hint = '';
1248
+ if (__DEV__) {
1249
+ if (
1250
+ Component !== null &&
1251
+ typeof Component === 'object' &&
1252
+ Component.$$typeof === REACT_LAZY_TYPE
1253
+ ) {
1254
+ hint = ' Did you wrap a component in React.lazy() more than once?';
1255
+ }
1256
+ }
1257
+ // This message intentionally doesn't mention ForwardRef or MemoComponent
1258
+ // because the fact that it's a separate type of work is an
1259
+ // implementation detail.
1260
+ invariant(
1261
+ false,
1262
+ 'Element type is invalid. Received a promise that resolves to: %s. ' +
1263
+ 'Lazy element type must resolve to a class or function.%s',
1264
+ Component,
1265
+ hint,
1266
+ );
1267
+}
1268
+
1269
+function mountIncompleteClassComponent(
1270
+ _current,
1271
+ workInProgress,
1272
+ Component,
1273
+ nextProps,
1274
+ renderExpirationTime,
1275
+) {
1276
+ if (_current !== null) {
1277
+ // An incomplete component only mounts if it suspended inside a non-
1278
+ // concurrent tree, in an inconsistent state. We want to treat it like
1279
+ // a new mount, even though an empty version of it already committed.
1280
+ // Disconnect the alternate pointers.
1281
+ _current.alternate = null;
1282
+ workInProgress.alternate = null;
1283
+ // Since this is conceptually a new fiber, schedule a Placement effect
1284
+ workInProgress.effectTag |= Placement;
1285
+ }
1286
+
1287
+ // Promote the fiber to a class and try rendering again.
1288
+ workInProgress.tag = ClassComponent;
1289
+
1290
+ // The rest of this function is a fork of `updateClassComponent`
1291
+
1292
+ // Push context providers early to prevent context stack mismatches.
1293
+ // During mounting we don't know the child context yet as the instance doesn't exist.
1294
+ // We will invalidate the child context in finishClassComponent() right after rendering.
1295
+ let hasContext;
1296
+ if (isLegacyContextProvider(Component)) {
1297
+ hasContext = true;
1298
+ pushLegacyContextProvider(workInProgress);
1299
+ } else {
1300
+ hasContext = false;
1301
+ }
1302
+ prepareToReadContext(workInProgress, renderExpirationTime);
1303
+
1304
+ constructClassInstance(workInProgress, Component, nextProps);
1305
+ mountClassInstance(
1306
+ workInProgress,
1307
+ Component,
1308
+ nextProps,
1309
+ renderExpirationTime,
1310
+ );
1311
+
1312
+ return finishClassComponent(
1313
+ null,
1314
+ workInProgress,
1315
+ Component,
1316
+ true,
1317
+ hasContext,
1318
+ renderExpirationTime,
1319
+ );
1320
+}
1321
+
1322
+function mountIndeterminateComponent(
1323
+ _current,
1324
+ workInProgress,
1325
+ Component,
1326
+ renderExpirationTime,
1327
+) {
1328
+ if (_current !== null) {
1329
+ // An indeterminate component only mounts if it suspended inside a non-
1330
+ // concurrent tree, in an inconsistent state. We want to treat it like
1331
+ // a new mount, even though an empty version of it already committed.
1332
+ // Disconnect the alternate pointers.
1333
+ _current.alternate = null;
1334
+ workInProgress.alternate = null;
1335
+ // Since this is conceptually a new fiber, schedule a Placement effect
1336
+ workInProgress.effectTag |= Placement;
1337
+ }
1338
+
1339
+ const props = workInProgress.pendingProps;
1340
+ let context;
1341
+ if (!disableLegacyContext) {
1342
+ const unmaskedContext = getUnmaskedContext(
1343
+ workInProgress,
1344
+ Component,
1345
+ false,
1346
+ );
1347
+ context = getMaskedContext(workInProgress, unmaskedContext);
1348
+ }
1349
+
1350
+ prepareToReadContext(workInProgress, renderExpirationTime);
1351
+ let value;
1352
+
1353
+ if (__DEV__) {
1354
+ if (
1355
+ Component.prototype &&
1356
+ typeof Component.prototype.render === 'function'
1357
+ ) {
1358
+ const componentName = getComponentName(Component) || 'Unknown';
1359
+
1360
+ if (!didWarnAboutBadClass[componentName]) {
1361
+ console.error(
1362
+ "The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
1363
+ 'This is likely to cause errors. Change %s to extend React.Component instead.',
1364
+ componentName,
1365
+ componentName,
1366
+ );
1367
+ didWarnAboutBadClass[componentName] = true;
1368
+ }
1369
+ }
1370
+
1371
+ if (workInProgress.mode & StrictMode) {
1372
+ ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null);
1373
+ }
1374
+
1375
+ setIsRendering(true);
1376
+ ReactCurrentOwner.current = workInProgress;
1377
+ value = renderWithHooks(
1378
+ null,
1379
+ workInProgress,
1380
+ Component,
1381
+ props,
1382
+ context,
1383
+ renderExpirationTime,
1384
+ );
1385
+ setIsRendering(false);
1386
+ } else {
1387
+ value = renderWithHooks(
1388
+ null,
1389
+ workInProgress,
1390
+ Component,
1391
+ props,
1392
+ context,
1393
+ renderExpirationTime,
1394
+ );
1395
+ }
1396
+ // React DevTools reads this flag.
1397
+ workInProgress.effectTag |= PerformedWork;
1398
+
1399
+ if (__DEV__) {
1400
+ // Support for module components is deprecated and is removed behind a flag.
1401
+ // Whether or not it would crash later, we want to show a good message in DEV first.
1402
+ if (
1403
+ typeof value === 'object' &&
1404
+ value !== null &&
1405
+ typeof value.render === 'function' &&
1406
+ value.$$typeof === undefined
1407
+ ) {
1408
+ const componentName = getComponentName(Component) || 'Unknown';
1409
+ if (!didWarnAboutModulePatternComponent[componentName]) {
1410
+ console.error(
1411
+ 'The <%s /> component appears to be a function component that returns a class instance. ' +
1412
+ 'Change %s to a class that extends React.Component instead. ' +
1413
+ "If you can't use a class try assigning the prototype on the function as a workaround. " +
1414
+ "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
1415
+ 'cannot be called with `new` by React.',
1416
+ componentName,
1417
+ componentName,
1418
+ componentName,
1419
+ );
1420
+ didWarnAboutModulePatternComponent[componentName] = true;
1421
+ }
1422
+ }
1423
+ }
1424
+
1425
+ if (
1426
+ // Run these checks in production only if the flag is off.
1427
+ // Eventually we'll delete this branch altogether.
1428
+ !disableModulePatternComponents &&
1429
+ typeof value === 'object' &&
1430
+ value !== null &&
1431
+ typeof value.render === 'function' &&
1432
+ value.$$typeof === undefined
1433
+ ) {
1434
+ if (__DEV__) {
1435
+ const componentName = getComponentName(Component) || 'Unknown';
1436
+ if (!didWarnAboutModulePatternComponent[componentName]) {
1437
+ console.error(
1438
+ 'The <%s /> component appears to be a function component that returns a class instance. ' +
1439
+ 'Change %s to a class that extends React.Component instead. ' +
1440
+ "If you can't use a class try assigning the prototype on the function as a workaround. " +
1441
+ "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
1442
+ 'cannot be called with `new` by React.',
1443
+ componentName,
1444
+ componentName,
1445
+ componentName,
1446
+ );
1447
+ didWarnAboutModulePatternComponent[componentName] = true;
1448
+ }
1449
+ }
1450
+
1451
+ // Proceed under the assumption that this is a class instance
1452
+ workInProgress.tag = ClassComponent;
1453
+
1454
+ // Throw out any hooks that were used.
1455
+ workInProgress.memoizedState = null;
1456
+ workInProgress.updateQueue = null;
1457
+
1458
+ // Push context providers early to prevent context stack mismatches.
1459
+ // During mounting we don't know the child context yet as the instance doesn't exist.
1460
+ // We will invalidate the child context in finishClassComponent() right after rendering.
1461
+ let hasContext = false;
1462
+ if (isLegacyContextProvider(Component)) {
1463
+ hasContext = true;
1464
+ pushLegacyContextProvider(workInProgress);
1465
+ } else {
1466
+ hasContext = false;
1467
+ }
1468
+
1469
+ workInProgress.memoizedState =
1470
+ value.state !== null && value.state !== undefined ? value.state : null;
1471
+
1472
+ initializeUpdateQueue(workInProgress);
1473
+
1474
+ const getDerivedStateFromProps = Component.getDerivedStateFromProps;
1475
+ if (typeof getDerivedStateFromProps === 'function') {
1476
+ applyDerivedStateFromProps(
1477
+ workInProgress,
1478
+ Component,
1479
+ getDerivedStateFromProps,
1480
+ props,
1481
+ );
1482
+ }
1483
+
1484
+ adoptClassInstance(workInProgress, value);
1485
+ mountClassInstance(workInProgress, Component, props, renderExpirationTime);
1486
+ return finishClassComponent(
1487
+ null,
1488
+ workInProgress,
1489
+ Component,
1490
+ true,
1491
+ hasContext,
1492
+ renderExpirationTime,
1493
+ );
1494
+ } else {
1495
+ // Proceed under the assumption that this is a function component
1496
+ workInProgress.tag = FunctionComponent;
1497
+ if (__DEV__) {
1498
+ if (disableLegacyContext && Component.contextTypes) {
1499
+ console.error(
1500
+ '%s uses the legacy contextTypes API which is no longer supported. ' +
1501
+ 'Use React.createContext() with React.useContext() instead.',
1502
+ getComponentName(Component) || 'Unknown',
1503
+ );
1504
+ }
1505
+
1506
+ if (
1507
+ debugRenderPhaseSideEffectsForStrictMode &&
1508
+ workInProgress.mode & StrictMode
1509
+ ) {
1510
+ disableLogs();
1511
+ try {
1512
+ value = renderWithHooks(
1513
+ null,
1514
+ workInProgress,
1515
+ Component,
1516
+ props,
1517
+ context,
1518
+ renderExpirationTime,
1519
+ );
1520
+ } finally {
1521
+ reenableLogs();
1522
+ }
1523
+ }
1524
+ }
1525
+ reconcileChildren(null, workInProgress, value, renderExpirationTime);
1526
+ if (__DEV__) {
1527
+ validateFunctionComponentInDev(workInProgress, Component);
1528
+ }
1529
+ return workInProgress.child;
1530
+ }
1531
+}
1532
+
1533
+function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
1534
+ if (__DEV__) {
1535
+ if (Component) {
1536
+ if (Component.childContextTypes) {
1537
+ console.error(
1538
+ '%s(...): childContextTypes cannot be defined on a function component.',
1539
+ Component.displayName || Component.name || 'Component',
1540
+ );
1541
+ }
1542
+ }
1543
+ if (workInProgress.ref !== null) {
1544
+ let info = '';
1545
+ const ownerName = getCurrentFiberOwnerNameInDevOrNull();
1546
+ if (ownerName) {
1547
+ info += '\n\nCheck the render method of `' + ownerName + '`.';
1548
+ }
1549
+
1550
+ let warningKey = ownerName || workInProgress._debugID || '';
1551
+ const debugSource = workInProgress._debugSource;
1552
+ if (debugSource) {
1553
+ warningKey = debugSource.fileName + ':' + debugSource.lineNumber;
1554
+ }
1555
+ if (!didWarnAboutFunctionRefs[warningKey]) {
1556
+ didWarnAboutFunctionRefs[warningKey] = true;
1557
+ console.error(
1558
+ 'Function components cannot be given refs. ' +
1559
+ 'Attempts to access this ref will fail. ' +
1560
+ 'Did you mean to use React.forwardRef()?%s',
1561
+ info,
1562
+ );
1563
+ }
1564
+ }
1565
+
1566
+ if (
1567
+ warnAboutDefaultPropsOnFunctionComponents &&
1568
+ Component.defaultProps !== undefined
1569
+ ) {
1570
+ const componentName = getComponentName(Component) || 'Unknown';
1571
+
1572
+ if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
1573
+ console.error(
1574
+ '%s: Support for defaultProps will be removed from function components ' +
1575
+ 'in a future major release. Use JavaScript default parameters instead.',
1576
+ componentName,
1577
+ );
1578
+ didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true;
1579
+ }
1580
+ }
1581
+
1582
+ if (typeof Component.getDerivedStateFromProps === 'function') {
1583
+ const componentName = getComponentName(Component) || 'Unknown';
1584
+
1585
+ if (!didWarnAboutGetDerivedStateOnFunctionComponent[componentName]) {
1586
+ console.error(
1587
+ '%s: Function components do not support getDerivedStateFromProps.',
1588
+ componentName,
1589
+ );
1590
+ didWarnAboutGetDerivedStateOnFunctionComponent[componentName] = true;
1591
+ }
1592
+ }
1593
+
1594
+ if (
1595
+ typeof Component.contextType === 'object' &&
1596
+ Component.contextType !== null
1597
+ ) {
1598
+ const componentName = getComponentName(Component) || 'Unknown';
1599
+
1600
+ if (!didWarnAboutContextTypeOnFunctionComponent[componentName]) {
1601
+ console.error(
1602
+ '%s: Function components do not support contextType.',
1603
+ componentName,
1604
+ );
1605
+ didWarnAboutContextTypeOnFunctionComponent[componentName] = true;
1606
+ }
1607
+ }
1608
+ }
1609
+}
1610
+
1611
+function mountSuspenseState(
1612
+ renderExpirationTime: ExpirationTime,
1613
+): SuspenseState {
1614
+ return {
1615
+ dehydrated: null,
1616
+ baseTime: renderExpirationTime,
1617
+ retryTime: NoWork,
1618
+ };
1619
+}
1620
+
1621
+function updateSuspenseState(
1622
+ prevSuspenseState: SuspenseState,
1623
+ renderExpirationTime: ExpirationTime,
1624
+): SuspenseState {
1625
+ const prevSuspendedTime = prevSuspenseState.baseTime;
1626
+ return {
1627
+ dehydrated: null,
1628
+ baseTime:
1629
+ // Choose whichever time is inclusive of the other one. This represents
1630
+ // the union of all the levels that suspended.
1631
+ prevSuspendedTime !== NoWork && prevSuspendedTime < renderExpirationTime
1632
+ ? prevSuspendedTime
1633
+ : renderExpirationTime,
1634
+ retryTime: NoWork,
1635
+ };
1636
+}
1637
+
1638
+function shouldRemainOnFallback(
1639
+ suspenseContext: SuspenseContext,
1640
+ current: null | Fiber,
1641
+ workInProgress: Fiber,
1642
+ renderExpirationTime: ExpirationTime,
1643
+) {
1644
+ // If we're already showing a fallback, there are cases where we need to
1645
+ // remain on that fallback regardless of whether the content has resolved.
1646
+ // For example, SuspenseList coordinates when nested content appears.
1647
+ if (current !== null) {
1648
+ const suspenseState: SuspenseState = current.memoizedState;
1649
+ if (suspenseState !== null) {
1650
+ // Currently showing a fallback. If the current render includes
1651
+ // the level that triggered the fallback, we must continue showing it,
1652
+ // regardless of what the Suspense context says.
1653
+ const baseTime = suspenseState.baseTime;
1654
+ if (baseTime !== NoWork && baseTime < renderExpirationTime) {
1655
+ return true;
1656
+ }
1657
+ // Otherwise, fall through to check the Suspense context.
1658
+ } else {
1659
+ // Currently showing content. Don't hide it, even if ForceSuspenseFallack
1660
+ // is true. More precise name might be "ForceRemainSuspenseFallback".
1661
+ // Note: This is a factoring smell. Can't remain on a fallback if there's
1662
+ // no fallback to remain on.
1663
+ return false;
1664
+ }
1665
+ }
1666
+ // Not currently showing content. Consult the Suspense context.
1667
+ return hasSuspenseContext(
1668
+ suspenseContext,
1669
+ (ForceSuspenseFallback: SuspenseContext),
1670
+ );
1671
+}
1672
+
1673
+function getRemainingWorkInPrimaryTree(
1674
+ current: Fiber,
1675
+ workInProgress: Fiber,
1676
+ renderExpirationTime,
1677
+) {
1678
+ const currentChildExpirationTime = current.childExpirationTime;
1679
+ const currentSuspenseState: SuspenseState = current.memoizedState;
1680
+ if (currentSuspenseState !== null) {
1681
+ // This boundary already timed out. Check if this render includes the level
1682
+ // that previously suspended.
1683
+ const baseTime = currentSuspenseState.baseTime;
1684
+ if (
1685
+ baseTime !== NoWork &&
1686
+ baseTime < renderExpirationTime &&
1687
+ baseTime > currentChildExpirationTime
1688
+ ) {
1689
+ // There's pending work at a lower level that might now be unblocked.
1690
+ return baseTime;
1691
+ }
1692
+ }
1693
+
1694
+ if (currentChildExpirationTime < renderExpirationTime) {
1695
+ // The highest priority remaining work is not part of this render. So the
1696
+ // remaining work has not changed.
1697
+ return currentChildExpirationTime;
1698
+ }
1699
+
1700
+ if ((workInProgress.mode & BlockingMode) !== NoMode) {
1701
+ // The highest priority remaining work is part of this render. Since we only
1702
+ // keep track of the highest level, we don't know if there's a lower
1703
+ // priority level scheduled. As a compromise, we'll render at the lowest
1704
+ // known level in the entire tree, since that will include everything.
1705
+ // TODO: If expirationTime were a bitmask where each bit represents a
1706
+ // separate task thread, this would be: currentChildBits & ~renderBits
1707
+ const root = getWorkInProgressRoot();
1708
+ if (root !== null) {
1709
+ const lastPendingTime = root.lastPendingTime;
1710
+ if (lastPendingTime < renderExpirationTime) {
1711
+ return lastPendingTime;
1712
+ }
1713
+ }
1714
+ }
1715
+ // In legacy mode, there's no work left.
1716
+ return NoWork;
1717
+}
1718
+
1719
+function updateSuspenseComponent(
1720
+ current,
1721
+ workInProgress,
1722
+ renderExpirationTime,
1723
+) {
1724
+ const mode = workInProgress.mode;
1725
+ const nextProps = workInProgress.pendingProps;
1726
+
1727
+ // This is used by DevTools to force a boundary to suspend.
1728
+ if (__DEV__) {
1729
+ if (shouldSuspend(workInProgress)) {
1730
+ workInProgress.effectTag |= DidCapture;
1731
+ }
1732
+ }
1733
+
1734
+ let suspenseContext: SuspenseContext = suspenseStackCursor.current;
1735
+
1736
+ let nextDidTimeout = false;
1737
+ const didSuspend = (workInProgress.effectTag & DidCapture) !== NoEffect;
1738
+
1739
+ if (
1740
+ didSuspend ||
1741
+ shouldRemainOnFallback(
1742
+ suspenseContext,
1743
+ current,
1744
+ workInProgress,
1745
+ renderExpirationTime,
1746
+ )
1747
+ ) {
1748
+ // Something in this boundary's subtree already suspended. Switch to
1749
+ // rendering the fallback children.
1750
+ nextDidTimeout = true;
1751
+ workInProgress.effectTag &= ~DidCapture;
1752
+ } else {
1753
+ // Attempting the main content
1754
+ if (
1755
+ current === null ||
1756
+ (current.memoizedState: null | SuspenseState) !== null
1757
+ ) {
1758
+ // This is a new mount or this boundary is already showing a fallback state.
1759
+ // Mark this subtree context as having at least one invisible parent that could
1760
+ // handle the fallback state.
1761
+ // Boundaries without fallbacks or should be avoided are not considered since
1762
+ // they cannot handle preferred fallback states.
1763
+ if (
1764
+ nextProps.fallback !== undefined &&
1765
+ nextProps.unstable_avoidThisFallback !== true
1766
+ ) {
1767
+ suspenseContext = addSubtreeSuspenseContext(
1768
+ suspenseContext,
1769
+ InvisibleParentSuspenseContext,
1770
+ );
1771
+ }
1772
+ }
1773
+ }
1774
+
1775
+ suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
1776
+
1777
+ pushSuspenseContext(workInProgress, suspenseContext);
1778
+
1779
+ // This next part is a bit confusing. If the children timeout, we switch to
1780
+ // showing the fallback children in place of the "primary" children.
1781
+ // However, we don't want to delete the primary children because then their
1782
+ // state will be lost (both the React state and the host state, e.g.
1783
+ // uncontrolled form inputs). Instead we keep them mounted and hide them.
1784
+ // Both the fallback children AND the primary children are rendered at the
1785
+ // same time. Once the primary children are un-suspended, we can delete
1786
+ // the fallback children — don't need to preserve their state.
1787
+ //
1788
+ // The two sets of children are siblings in the host environment, but
1789
+ // semantically, for purposes of reconciliation, they are two separate sets.
1790
+ // So we store them using two fragment fibers.
1791
+ //
1792
+ // However, we want to avoid allocating extra fibers for every placeholder.
1793
+ // They're only necessary when the children time out, because that's the
1794
+ // only time when both sets are mounted.
1795
+ //
1796
+ // So, the extra fragment fibers are only used if the children time out.
1797
+ // Otherwise, we render the primary children directly. This requires some
1798
+ // custom reconciliation logic to preserve the state of the primary
1799
+ // children. It's essentially a very basic form of re-parenting.
1800
+
1801
+ if (current === null) {
1802
+ // If we're currently hydrating, try to hydrate this boundary.
1803
+ // But only if this has a fallback.
1804
+ if (nextProps.fallback !== undefined) {
1805
+ tryToClaimNextHydratableInstance(workInProgress);
1806
+ // This could've been a dehydrated suspense component.
1807
+ if (enableSuspenseServerRenderer) {
1808
+ const suspenseState: null | SuspenseState =
1809
+ workInProgress.memoizedState;
1810
+ if (suspenseState !== null) {
1811
+ const dehydrated = suspenseState.dehydrated;
1812
+ if (dehydrated !== null) {
1813
+ return mountDehydratedSuspenseComponent(
1814
+ workInProgress,
1815
+ dehydrated,
1816
+ renderExpirationTime,
1817
+ );
1818
+ }
1819
+ }
1820
+ }
1821
+ }
1822
+
1823
+ // This is the initial mount. This branch is pretty simple because there's
1824
+ // no previous state that needs to be preserved.
1825
+ if (nextDidTimeout) {
1826
+ // Mount separate fragments for primary and fallback children.
1827
+ const nextFallbackChildren = nextProps.fallback;
1828
+ const primaryChildFragment = createFiberFromFragment(
1829
+ null,
1830
+ mode,
1831
+ NoWork,
1832
+ null,
1833
+ );
1834
+ primaryChildFragment.return = workInProgress;
1835
+
1836
+ if ((workInProgress.mode & BlockingMode) === NoMode) {
1837
+ // Outside of blocking mode, we commit the effects from the
1838
+ // partially completed, timed-out tree, too.
1839
+ const progressedState: SuspenseState = workInProgress.memoizedState;
1840
+ const progressedPrimaryChild: Fiber | null =
1841
+ progressedState !== null
1842
+ ? (workInProgress.child: any).child
1843
+ : (workInProgress.child: any);
1844
+ primaryChildFragment.child = progressedPrimaryChild;
1845
+ let progressedChild = progressedPrimaryChild;
1846
+ while (progressedChild !== null) {
1847
+ progressedChild.return = primaryChildFragment;
1848
+ progressedChild = progressedChild.sibling;
1849
+ }
1850
+ }
1851
+
1852
+ const fallbackChildFragment = createFiberFromFragment(
1853
+ nextFallbackChildren,
1854
+ mode,
1855
+ renderExpirationTime,
1856
+ null,
1857
+ );
1858
+ fallbackChildFragment.return = workInProgress;
1859
+ primaryChildFragment.sibling = fallbackChildFragment;
1860
+ // Skip the primary children, and continue working on the
1861
+ // fallback children.
1862
+ workInProgress.memoizedState = mountSuspenseState(renderExpirationTime);
1863
+ workInProgress.child = primaryChildFragment;
1864
+ return fallbackChildFragment;
1865
+ } else {
1866
+ // Mount the primary children without an intermediate fragment fiber.
1867
+ const nextPrimaryChildren = nextProps.children;
1868
+ workInProgress.memoizedState = null;
1869
+ return (workInProgress.child = mountChildFibers(
1870
+ workInProgress,
1871
+ null,
1872
+ nextPrimaryChildren,
1873
+ renderExpirationTime,
1874
+ ));
1875
+ }
1876
+ } else {
1877
+ // This is an update. This branch is more complicated because we need to
1878
+ // ensure the state of the primary children is preserved.
1879
+ const prevState: null | SuspenseState = current.memoizedState;
1880
+ if (prevState !== null) {
1881
+ if (enableSuspenseServerRenderer) {
1882
+ const dehydrated = prevState.dehydrated;
1883
+ if (dehydrated !== null) {
1884
+ if (!didSuspend) {
1885
+ return updateDehydratedSuspenseComponent(
1886
+ current,
1887
+ workInProgress,
1888
+ dehydrated,
1889
+ prevState,
1890
+ renderExpirationTime,
1891
+ );
1892
+ } else if (
1893
+ (workInProgress.memoizedState: null | SuspenseState) !== null
1894
+ ) {
1895
+ // Something suspended and we should still be in dehydrated mode.
1896
+ // Leave the existing child in place.
1897
+ workInProgress.child = current.child;
1898
+ // The dehydrated completion pass expects this flag to be there
1899
+ // but the normal suspense pass doesn't.
1900
+ workInProgress.effectTag |= DidCapture;
1901
+ return null;
1902
+ } else {
1903
+ // Suspended but we should no longer be in dehydrated mode.
1904
+ // Therefore we now have to render the fallback. Wrap the children
1905
+ // in a fragment fiber to keep them separate from the fallback
1906
+ // children.
1907
+ const nextFallbackChildren = nextProps.fallback;
1908
+ const primaryChildFragment = createFiberFromFragment(
1909
+ // It shouldn't matter what the pending props are because we aren't
1910
+ // going to render this fragment.
1911
+ null,
1912
+ mode,
1913
+ NoWork,
1914
+ null,
1915
+ );
1916
+ primaryChildFragment.return = workInProgress;
1917
+
1918
+ // This is always null since we never want the previous child
1919
+ // that we're not going to hydrate.
1920
+ primaryChildFragment.child = null;
1921
+
1922
+ if ((workInProgress.mode & BlockingMode) === NoMode) {
1923
+ // Outside of blocking mode, we commit the effects from the
1924
+ // partially completed, timed-out tree, too.
1925
+ let progressedChild = (primaryChildFragment.child =
1926
+ workInProgress.child);
1927
+ while (progressedChild !== null) {
1928
+ progressedChild.return = primaryChildFragment;
1929
+ progressedChild = progressedChild.sibling;
1930
+ }
1931
+ } else {
1932
+ // We will have dropped the effect list which contains the deletion.
1933
+ // We need to reconcile to delete the current child.
1934
+ reconcileChildFibers(
1935
+ workInProgress,
1936
+ current.child,
1937
+ null,
1938
+ renderExpirationTime,
1939
+ );
1940
+ }
1941
+
1942
+ // Because primaryChildFragment is a new fiber that we're inserting as the
1943
+ // parent of a new tree, we need to set its treeBaseDuration.
1944
+ if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
1945
+ // treeBaseDuration is the sum of all the child tree base durations.
1946
+ let treeBaseDuration = 0;
1947
+ let hiddenChild = primaryChildFragment.child;
1948
+ while (hiddenChild !== null) {
1949
+ treeBaseDuration += hiddenChild.treeBaseDuration;
1950
+ hiddenChild = hiddenChild.sibling;
1951
+ }
1952
+ primaryChildFragment.treeBaseDuration = treeBaseDuration;
1953
+ }
1954
+
1955
+ // Create a fragment from the fallback children, too.
1956
+ const fallbackChildFragment = createFiberFromFragment(
1957
+ nextFallbackChildren,
1958
+ mode,
1959
+ renderExpirationTime,
1960
+ null,
1961
+ );
1962
+ fallbackChildFragment.return = workInProgress;
1963
+ primaryChildFragment.sibling = fallbackChildFragment;
1964
+ fallbackChildFragment.effectTag |= Placement;
1965
+ primaryChildFragment.childExpirationTime = getRemainingWorkInPrimaryTree(
1966
+ current,
1967
+ workInProgress,
1968
+ renderExpirationTime,
1969
+ );
1970
+ workInProgress.memoizedState = updateSuspenseState(
1971
+ current.memoizedState,
1972
+ renderExpirationTime,
1973
+ );
1974
+ workInProgress.child = primaryChildFragment;
1975
+
1976
+ // Skip the primary children, and continue working on the
1977
+ // fallback children.
1978
+ return fallbackChildFragment;
1979
+ }
1980
+ }
1981
+ }
1982
+ // The current tree already timed out. That means each child set is
1983
+ // wrapped in a fragment fiber.
1984
+ const currentPrimaryChildFragment: Fiber = (current.child: any);
1985
+ const currentFallbackChildFragment: Fiber = (currentPrimaryChildFragment.sibling: any);
1986
+ if (nextDidTimeout) {
1987
+ // Still timed out. Reuse the current primary children by cloning
1988
+ // its fragment. We're going to skip over these entirely.
1989
+ const nextFallbackChildren = nextProps.fallback;
1990
+ const primaryChildFragment = createWorkInProgress(
1991
+ currentPrimaryChildFragment,
1992
+ currentPrimaryChildFragment.pendingProps,
1993
+ );
1994
+ primaryChildFragment.return = workInProgress;
1995
+
1996
+ if ((workInProgress.mode & BlockingMode) === NoMode) {
1997
+ // Outside of blocking mode, we commit the effects from the
1998
+ // partially completed, timed-out tree, too.
1999
+ const progressedState: SuspenseState = workInProgress.memoizedState;
2000
+ const progressedPrimaryChild: Fiber | null =
2001
+ progressedState !== null
2002
+ ? (workInProgress.child: any).child
2003
+ : (workInProgress.child: any);
2004
+ if (progressedPrimaryChild !== currentPrimaryChildFragment.child) {
2005
+ primaryChildFragment.child = progressedPrimaryChild;
2006
+ let progressedChild = progressedPrimaryChild;
2007
+ while (progressedChild !== null) {
2008
+ progressedChild.return = primaryChildFragment;
2009
+ progressedChild = progressedChild.sibling;
2010
+ }
2011
+ }
2012
+ }
2013
+
2014
+ // Because primaryChildFragment is a new fiber that we're inserting as the
2015
+ // parent of a new tree, we need to set its treeBaseDuration.
2016
+ if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
2017
+ // treeBaseDuration is the sum of all the child tree base durations.
2018
+ let treeBaseDuration = 0;
2019
+ let hiddenChild = primaryChildFragment.child;
2020
+ while (hiddenChild !== null) {
2021
+ treeBaseDuration += hiddenChild.treeBaseDuration;
2022
+ hiddenChild = hiddenChild.sibling;
2023
+ }
2024
+ primaryChildFragment.treeBaseDuration = treeBaseDuration;
2025
+ }
2026
+
2027
+ // Clone the fallback child fragment, too. These we'll continue
2028
+ // working on.
2029
+ const fallbackChildFragment = createWorkInProgress(
2030
+ currentFallbackChildFragment,
2031
+ nextFallbackChildren,
2032
+ );
2033
+ fallbackChildFragment.return = workInProgress;
2034
+ primaryChildFragment.sibling = fallbackChildFragment;
2035
+ primaryChildFragment.childExpirationTime = getRemainingWorkInPrimaryTree(
2036
+ current,
2037
+ workInProgress,
2038
+ renderExpirationTime,
2039
+ );
2040
+ // Skip the primary children, and continue working on the
2041
+ // fallback children.
2042
+ workInProgress.memoizedState = updateSuspenseState(
2043
+ current.memoizedState,
2044
+ renderExpirationTime,
2045
+ );
2046
+ workInProgress.child = primaryChildFragment;
2047
+ return fallbackChildFragment;
2048
+ } else {
2049
+ // No longer suspended. Switch back to showing the primary children,
2050
+ // and remove the intermediate fragment fiber.
2051
+ const nextPrimaryChildren = nextProps.children;
2052
+ const currentPrimaryChild = currentPrimaryChildFragment.child;
2053
+ const primaryChild = reconcileChildFibers(
2054
+ workInProgress,
2055
+ currentPrimaryChild,
2056
+ nextPrimaryChildren,
2057
+ renderExpirationTime,
2058
+ );
2059
+
2060
+ // If this render doesn't suspend, we need to delete the fallback
2061
+ // children. Wait until the complete phase, after we've confirmed the
2062
+ // fallback is no longer needed.
2063
+ // TODO: Would it be better to store the fallback fragment on
2064
+ // the stateNode?
2065
+
2066
+ // Continue rendering the children, like we normally do.
2067
+ workInProgress.memoizedState = null;
2068
+ return (workInProgress.child = primaryChild);
2069
+ }
2070
+ } else {
2071
+ // The current tree has not already timed out. That means the primary
2072
+ // children are not wrapped in a fragment fiber.
2073
+ const currentPrimaryChild = current.child;
2074
+ if (nextDidTimeout) {
2075
+ // Timed out. Wrap the children in a fragment fiber to keep them
2076
+ // separate from the fallback children.
2077
+ const nextFallbackChildren = nextProps.fallback;
2078
+ const primaryChildFragment = createFiberFromFragment(
2079
+ // It shouldn't matter what the pending props are because we aren't
2080
+ // going to render this fragment.
2081
+ null,
2082
+ mode,
2083
+ NoWork,
2084
+ null,
2085
+ );
2086
+ primaryChildFragment.return = workInProgress;
2087
+ primaryChildFragment.child = currentPrimaryChild;
2088
+ if (currentPrimaryChild !== null) {
2089
+ currentPrimaryChild.return = primaryChildFragment;
2090
+ }
2091
+
2092
+ // Even though we're creating a new fiber, there are no new children,
2093
+ // because we're reusing an already mounted tree. So we don't need to
2094
+ // schedule a placement.
2095
+ // primaryChildFragment.effectTag |= Placement;
2096
+
2097
+ if ((workInProgress.mode & BlockingMode) === NoMode) {
2098
+ // Outside of blocking mode, we commit the effects from the
2099
+ // partially completed, timed-out tree, too.
2100
+ const progressedState: SuspenseState = workInProgress.memoizedState;
2101
+ const progressedPrimaryChild: Fiber | null =
2102
+ progressedState !== null
2103
+ ? (workInProgress.child: any).child
2104
+ : (workInProgress.child: any);
2105
+ primaryChildFragment.child = progressedPrimaryChild;
2106
+ let progressedChild = progressedPrimaryChild;
2107
+ while (progressedChild !== null) {
2108
+ progressedChild.return = primaryChildFragment;
2109
+ progressedChild = progressedChild.sibling;
2110
+ }
2111
+ }
2112
+
2113
+ // Because primaryChildFragment is a new fiber that we're inserting as the
2114
+ // parent of a new tree, we need to set its treeBaseDuration.
2115
+ if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
2116
+ // treeBaseDuration is the sum of all the child tree base durations.
2117
+ let treeBaseDuration = 0;
2118
+ let hiddenChild = primaryChildFragment.child;
2119
+ while (hiddenChild !== null) {
2120
+ treeBaseDuration += hiddenChild.treeBaseDuration;
2121
+ hiddenChild = hiddenChild.sibling;
2122
+ }
2123
+ primaryChildFragment.treeBaseDuration = treeBaseDuration;
2124
+ }
2125
+
2126
+ // Create a fragment from the fallback children, too.
2127
+ const fallbackChildFragment = createFiberFromFragment(
2128
+ nextFallbackChildren,
2129
+ mode,
2130
+ renderExpirationTime,
2131
+ null,
2132
+ );
2133
+ fallbackChildFragment.return = workInProgress;
2134
+ primaryChildFragment.sibling = fallbackChildFragment;
2135
+ fallbackChildFragment.effectTag |= Placement;
2136
+ primaryChildFragment.childExpirationTime = getRemainingWorkInPrimaryTree(
2137
+ current,
2138
+ workInProgress,
2139
+ renderExpirationTime,
2140
+ );
2141
+ // Skip the primary children, and continue working on the
2142
+ // fallback children.
2143
+ workInProgress.memoizedState = mountSuspenseState(renderExpirationTime);
2144
+ workInProgress.child = primaryChildFragment;
2145
+ return fallbackChildFragment;
2146
+ } else {
2147
+ // Still haven't timed out. Continue rendering the children, like we
2148
+ // normally do.
2149
+ workInProgress.memoizedState = null;
2150
+ const nextPrimaryChildren = nextProps.children;
2151
+ return (workInProgress.child = reconcileChildFibers(
2152
+ workInProgress,
2153
+ currentPrimaryChild,
2154
+ nextPrimaryChildren,
2155
+ renderExpirationTime,
2156
+ ));
2157
+ }
2158
+ }
2159
+ }
2160
+}
2161
+
2162
+function retrySuspenseComponentWithoutHydrating(
2163
+ current: Fiber,
2164
+ workInProgress: Fiber,
2165
+ renderExpirationTime: ExpirationTime,
2166
+) {
2167
+ // We're now not suspended nor dehydrated.
2168
+ workInProgress.memoizedState = null;
2169
+ // Retry with the full children.
2170
+ const nextProps = workInProgress.pendingProps;
2171
+ const nextChildren = nextProps.children;
2172
+ // This will ensure that the children get Placement effects and
2173
+ // that the old child gets a Deletion effect.
2174
+ // We could also call forceUnmountCurrentAndReconcile.
2175
+ reconcileChildren(
2176
+ current,
2177
+ workInProgress,
2178
+ nextChildren,
2179
+ renderExpirationTime,
2180
+ );
2181
+ return workInProgress.child;
2182
+}
2183
+
2184
+function mountDehydratedSuspenseComponent(
2185
+ workInProgress: Fiber,
2186
+ suspenseInstance: SuspenseInstance,
2187
+ renderExpirationTime: ExpirationTime,
2188
+): null | Fiber {
2189
+ // During the first pass, we'll bail out and not drill into the children.
2190
+ // Instead, we'll leave the content in place and try to hydrate it later.
2191
+ if ((workInProgress.mode & BlockingMode) === NoMode) {
2192
+ if (__DEV__) {
2193
+ console.error(
2194
+ 'Cannot hydrate Suspense in legacy mode. Switch from ' +
2195
+ 'ReactDOM.hydrate(element, container) to ' +
2196
+ 'ReactDOM.createBlockingRoot(container, { hydrate: true })' +
2197
+ '.render(element) or remove the Suspense components from ' +
2198
+ 'the server rendered components.',
2199
+ );
2200
+ }
2201
+ workInProgress.expirationTime = Sync;
2202
+ } else if (isSuspenseInstanceFallback(suspenseInstance)) {
2203
+ // This is a client-only boundary. Since we won't get any content from the server
2204
+ // for this, we need to schedule that at a higher priority based on when it would
2205
+ // have timed out. In theory we could render it in this pass but it would have the
2206
+ // wrong priority associated with it and will prevent hydration of parent path.
2207
+ // Instead, we'll leave work left on it to render it in a separate commit.
2208
+
2209
+ // TODO This time should be the time at which the server rendered response that is
2210
+ // a parent to this boundary was displayed. However, since we currently don't have
2211
+ // a protocol to transfer that time, we'll just estimate it by using the current
2212
+ // time. This will mean that Suspense timeouts are slightly shifted to later than
2213
+ // they should be.
2214
+ const serverDisplayTime = requestCurrentTimeForUpdate();
2215
+ // Schedule a normal pri update to render this content.
2216
+ const newExpirationTime = computeAsyncExpiration(serverDisplayTime);
2217
+ if (enableSchedulerTracing) {
2218
+ markSpawnedWork(newExpirationTime);
2219
+ }
2220
+ workInProgress.expirationTime = newExpirationTime;
2221
+ } else {
2222
+ // We'll continue hydrating the rest at offscreen priority since we'll already
2223
+ // be showing the right content coming from the server, it is no rush.
2224
+ workInProgress.expirationTime = Never;
2225
+ if (enableSchedulerTracing) {
2226
+ markSpawnedWork(Never);
2227
+ }
2228
+ }
2229
+ return null;
2230
+}
2231
+
2232
+function updateDehydratedSuspenseComponent(
2233
+ current: Fiber,
2234
+ workInProgress: Fiber,
2235
+ suspenseInstance: SuspenseInstance,
2236
+ suspenseState: SuspenseState,
2237
+ renderExpirationTime: ExpirationTime,
2238
+): null | Fiber {
2239
+ // We should never be hydrating at this point because it is the first pass,
2240
+ // but after we've already committed once.
2241
+ warnIfHydrating();
2242
+
2243
+ if ((workInProgress.mode & BlockingMode) === NoMode) {
2244
+ return retrySuspenseComponentWithoutHydrating(
2245
+ current,
2246
+ workInProgress,
2247
+ renderExpirationTime,
2248
+ );
2249
+ }
2250
+
2251
+ if (isSuspenseInstanceFallback(suspenseInstance)) {
2252
+ // This boundary is in a permanent fallback state. In this case, we'll never
2253
+ // get an update and we'll never be able to hydrate the final content. Let's just try the
2254
+ // client side render instead.
2255
+ return retrySuspenseComponentWithoutHydrating(
2256
+ current,
2257
+ workInProgress,
2258
+ renderExpirationTime,
2259
+ );
2260
+ }
2261
+ // We use childExpirationTime to indicate that a child might depend on context, so if
2262
+ // any context has changed, we need to treat is as if the input might have changed.
2263
+ const hasContextChanged = current.childExpirationTime >= renderExpirationTime;
2264
+ if (didReceiveUpdate || hasContextChanged) {
2265
+ // This boundary has changed since the first render. This means that we are now unable to
2266
+ // hydrate it. We might still be able to hydrate it using an earlier expiration time, if
2267
+ // we are rendering at lower expiration than sync.
2268
+ if (renderExpirationTime < Sync) {
2269
+ if (suspenseState.retryTime <= renderExpirationTime) {
2270
+ // This render is even higher pri than we've seen before, let's try again
2271
+ // at even higher pri.
2272
+ const attemptHydrationAtExpirationTime = renderExpirationTime + 1;
2273
+ suspenseState.retryTime = attemptHydrationAtExpirationTime;
2274
+ scheduleUpdateOnFiber(current, attemptHydrationAtExpirationTime);
2275
+ // TODO: Early abort this render.
2276
+ } else {
2277
+ // We have already tried to ping at a higher priority than we're rendering with
2278
+ // so if we got here, we must have failed to hydrate at those levels. We must
2279
+ // now give up. Instead, we're going to delete the whole subtree and instead inject
2280
+ // a new real Suspense boundary to take its place, which may render content
2281
+ // or fallback. This might suspend for a while and if it does we might still have
2282
+ // an opportunity to hydrate before this pass commits.
2283
+ }
2284
+ }
2285
+ // If we have scheduled higher pri work above, this will probably just abort the render
2286
+ // since we now have higher priority work, but in case it doesn't, we need to prepare to
2287
+ // render something, if we time out. Even if that requires us to delete everything and
2288
+ // skip hydration.
2289
+ // Delay having to do this as long as the suspense timeout allows us.
2290
+ renderDidSuspendDelayIfPossible();
2291
+ return retrySuspenseComponentWithoutHydrating(
2292
+ current,
2293
+ workInProgress,
2294
+ renderExpirationTime,
2295
+ );
2296
+ } else if (isSuspenseInstancePending(suspenseInstance)) {
2297
+ // This component is still pending more data from the server, so we can't hydrate its
2298
+ // content. We treat it as if this component suspended itself. It might seem as if
2299
+ // we could just try to render it client-side instead. However, this will perform a
2300
+ // lot of unnecessary work and is unlikely to complete since it often will suspend
2301
+ // on missing data anyway. Additionally, the server might be able to render more
2302
+ // than we can on the client yet. In that case we'd end up with more fallback states
2303
+ // on the client than if we just leave it alone. If the server times out or errors
2304
+ // these should update this boundary to the permanent Fallback state instead.
2305
+ // Mark it as having captured (i.e. suspended).
2306
+ workInProgress.effectTag |= DidCapture;
2307
+ // Leave the child in place. I.e. the dehydrated fragment.
2308
+ workInProgress.child = current.child;
2309
+ // Register a callback to retry this boundary once the server has sent the result.
2310
+ registerSuspenseInstanceRetry(
2311
+ suspenseInstance,
2312
+ retryDehydratedSuspenseBoundary.bind(null, current),
2313
+ );
2314
+ return null;
2315
+ } else {
2316
+ // This is the first attempt.
2317
+ reenterHydrationStateFromDehydratedSuspenseInstance(
2318
+ workInProgress,
2319
+ suspenseInstance,
2320
+ );
2321
+ const nextProps = workInProgress.pendingProps;
2322
+ const nextChildren = nextProps.children;
2323
+ const child = mountChildFibers(
2324
+ workInProgress,
2325
+ null,
2326
+ nextChildren,
2327
+ renderExpirationTime,
2328
+ );
2329
+ let node = child;
2330
+ while (node) {
2331
+ // Mark each child as hydrating. This is a fast path to know whether this
2332
+ // tree is part of a hydrating tree. This is used to determine if a child
2333
+ // node has fully mounted yet, and for scheduling event replaying.
2334
+ // Conceptually this is similar to Placement in that a new subtree is
2335
+ // inserted into the React tree here. It just happens to not need DOM
2336
+ // mutations because it already exists.
2337
+ node.effectTag |= Hydrating;
2338
+ node = node.sibling;
2339
+ }
2340
+ workInProgress.child = child;
2341
+ return workInProgress.child;
2342
+ }
2343
+}
2344
+
2345
+function scheduleWorkOnFiber(
2346
+ fiber: Fiber,
2347
+ renderExpirationTime: ExpirationTime,
2348
+) {
2349
+ if (fiber.expirationTime < renderExpirationTime) {
2350
+ fiber.expirationTime = renderExpirationTime;
2351
+ }
2352
+ const alternate = fiber.alternate;
2353
+ if (alternate !== null && alternate.expirationTime < renderExpirationTime) {
2354
+ alternate.expirationTime = renderExpirationTime;
2355
+ }
2356
+ scheduleWorkOnParentPath(fiber.return, renderExpirationTime);
2357
+}
2358
+
2359
+function propagateSuspenseContextChange(
2360
+ workInProgress: Fiber,
2361
+ firstChild: null | Fiber,
2362
+ renderExpirationTime: ExpirationTime,
2363
+): void {
2364
+ // Mark any Suspense boundaries with fallbacks as having work to do.
2365
+ // If they were previously forced into fallbacks, they may now be able
2366
+ // to unblock.
2367
+ let node = firstChild;
2368
+ while (node !== null) {
2369
+ if (node.tag === SuspenseComponent) {
2370
+ const state: SuspenseState | null = node.memoizedState;
2371
+ if (state !== null) {
2372
+ scheduleWorkOnFiber(node, renderExpirationTime);
2373
+ }
2374
+ } else if (node.tag === SuspenseListComponent) {
2375
+ // If the tail is hidden there might not be an Suspense boundaries
2376
+ // to schedule work on. In this case we have to schedule it on the
2377
+ // list itself.
2378
+ // We don't have to traverse to the children of the list since
2379
+ // the list will propagate the change when it rerenders.
2380
+ scheduleWorkOnFiber(node, renderExpirationTime);
2381
+ } else if (node.child !== null) {
2382
+ node.child.return = node;
2383
+ node = node.child;
2384
+ continue;
2385
+ }
2386
+ if (node === workInProgress) {
2387
+ return;
2388
+ }
2389
+ while (node.sibling === null) {
2390
+ if (node.return === null || node.return === workInProgress) {
2391
+ return;
2392
+ }
2393
+ node = node.return;
2394
+ }
2395
+ node.sibling.return = node.return;
2396
+ node = node.sibling;
2397
+ }
2398
+}
2399
+
2400
+function findLastContentRow(firstChild: null | Fiber): null | Fiber {
2401
+ // This is going to find the last row among these children that is already
2402
+ // showing content on the screen, as opposed to being in fallback state or
2403
+ // new. If a row has multiple Suspense boundaries, any of them being in the
2404
+ // fallback state, counts as the whole row being in a fallback state.
2405
+ // Note that the "rows" will be workInProgress, but any nested children
2406
+ // will still be current since we haven't rendered them yet. The mounted
2407
+ // order may not be the same as the new order. We use the new order.
2408
+ let row = firstChild;
2409
+ let lastContentRow: null | Fiber = null;
2410
+ while (row !== null) {
2411
+ const currentRow = row.alternate;
2412
+ // New rows can't be content rows.
2413
+ if (currentRow !== null && findFirstSuspended(currentRow) === null) {
2414
+ lastContentRow = row;
2415
+ }
2416
+ row = row.sibling;
2417
+ }
2418
+ return lastContentRow;
2419
+}
2420
+
2421
+type SuspenseListRevealOrder = 'forwards' | 'backwards' | 'together' | void;
2422
+
2423
+function validateRevealOrder(revealOrder: SuspenseListRevealOrder) {
2424
+ if (__DEV__) {
2425
+ if (
2426
+ revealOrder !== undefined &&
2427
+ revealOrder !== 'forwards' &&
2428
+ revealOrder !== 'backwards' &&
2429
+ revealOrder !== 'together' &&
2430
+ !didWarnAboutRevealOrder[revealOrder]
2431
+ ) {
2432
+ didWarnAboutRevealOrder[revealOrder] = true;
2433
+ if (typeof revealOrder === 'string') {
2434
+ switch (revealOrder.toLowerCase()) {
2435
+ case 'together':
2436
+ case 'forwards':
2437
+ case 'backwards': {
2438
+ console.error(
2439
+ '"%s" is not a valid value for revealOrder on <SuspenseList />. ' +
2440
+ 'Use lowercase "%s" instead.',
2441
+ revealOrder,
2442
+ revealOrder.toLowerCase(),
2443
+ );
2444
+ break;
2445
+ }
2446
+ case 'forward':
2447
+ case 'backward': {
2448
+ console.error(
2449
+ '"%s" is not a valid value for revealOrder on <SuspenseList />. ' +
2450
+ 'React uses the -s suffix in the spelling. Use "%ss" instead.',
2451
+ revealOrder,
2452
+ revealOrder.toLowerCase(),
2453
+ );
2454
+ break;
2455
+ }
2456
+ default:
2457
+ console.error(
2458
+ '"%s" is not a supported revealOrder on <SuspenseList />. ' +
2459
+ 'Did you mean "together", "forwards" or "backwards"?',
2460
+ revealOrder,
2461
+ );
2462
+ break;
2463
+ }
2464
+ } else {
2465
+ console.error(
2466
+ '%s is not a supported value for revealOrder on <SuspenseList />. ' +
2467
+ 'Did you mean "together", "forwards" or "backwards"?',
2468
+ revealOrder,
2469
+ );
2470
+ }
2471
+ }
2472
+ }
2473
+}
2474
+
2475
+function validateTailOptions(
2476
+ tailMode: SuspenseListTailMode,
2477
+ revealOrder: SuspenseListRevealOrder,
2478
+) {
2479
+ if (__DEV__) {
2480
+ if (tailMode !== undefined && !didWarnAboutTailOptions[tailMode]) {
2481
+ if (tailMode !== 'collapsed' && tailMode !== 'hidden') {
2482
+ didWarnAboutTailOptions[tailMode] = true;
2483
+ console.error(
2484
+ '"%s" is not a supported value for tail on <SuspenseList />. ' +
2485
+ 'Did you mean "collapsed" or "hidden"?',
2486
+ tailMode,
2487
+ );
2488
+ } else if (revealOrder !== 'forwards' && revealOrder !== 'backwards') {
2489
+ didWarnAboutTailOptions[tailMode] = true;
2490
+ console.error(
2491
+ '<SuspenseList tail="%s" /> is only valid if revealOrder is ' +
2492
+ '"forwards" or "backwards". ' +
2493
+ 'Did you mean to specify revealOrder="forwards"?',
2494
+ tailMode,
2495
+ );
2496
+ }
2497
+ }
2498
+ }
2499
+}
2500
+
2501
+function validateSuspenseListNestedChild(childSlot: mixed, index: number) {
2502
+ if (__DEV__) {
2503
+ const isArray = Array.isArray(childSlot);
2504
+ const isIterable =
2505
+ !isArray && typeof getIteratorFn(childSlot) === 'function';
2506
+ if (isArray || isIterable) {
2507
+ const type = isArray ? 'array' : 'iterable';
2508
+ console.error(
2509
+ 'A nested %s was passed to row #%s in <SuspenseList />. Wrap it in ' +
2510
+ 'an additional SuspenseList to configure its revealOrder: ' +
2511
+ '<SuspenseList revealOrder=...> ... ' +
2512
+ '<SuspenseList revealOrder=...>{%s}</SuspenseList> ... ' +
2513
+ '</SuspenseList>',
2514
+ type,
2515
+ index,
2516
+ type,
2517
+ );
2518
+ return false;
2519
+ }
2520
+ }
2521
+ return true;
2522
+}
2523
+
2524
+function validateSuspenseListChildren(
2525
+ children: mixed,
2526
+ revealOrder: SuspenseListRevealOrder,
2527
+) {
2528
+ if (__DEV__) {
2529
+ if (
2530
+ (revealOrder === 'forwards' || revealOrder === 'backwards') &&
2531
+ children !== undefined &&
2532
+ children !== null &&
2533
+ children !== false
2534
+ ) {
2535
+ if (Array.isArray(children)) {
2536
+ for (let i = 0; i < children.length; i++) {
2537
+ if (!validateSuspenseListNestedChild(children[i], i)) {
2538
+ return;
2539
+ }
2540
+ }
2541
+ } else {
2542
+ const iteratorFn = getIteratorFn(children);
2543
+ if (typeof iteratorFn === 'function') {
2544
+ const childrenIterator = iteratorFn.call(children);
2545
+ if (childrenIterator) {
2546
+ let step = childrenIterator.next();
2547
+ let i = 0;
2548
+ for (; !step.done; step = childrenIterator.next()) {
2549
+ if (!validateSuspenseListNestedChild(step.value, i)) {
2550
+ return;
2551
+ }
2552
+ i++;
2553
+ }
2554
+ }
2555
+ } else {
2556
+ console.error(
2557
+ 'A single row was passed to a <SuspenseList revealOrder="%s" />. ' +
2558
+ 'This is not useful since it needs multiple rows. ' +
2559
+ 'Did you mean to pass multiple children or an array?',
2560
+ revealOrder,
2561
+ );
2562
+ }
2563
+ }
2564
+ }
2565
+ }
2566
+}
2567
+
2568
+function initSuspenseListRenderState(
2569
+ workInProgress: Fiber,
2570
+ isBackwards: boolean,
2571
+ tail: null | Fiber,
2572
+ lastContentRow: null | Fiber,
2573
+ tailMode: SuspenseListTailMode,
2574
+ lastEffectBeforeRendering: null | Fiber,
2575
+): void {
2576
+ const renderState: null | SuspenseListRenderState =
2577
+ workInProgress.memoizedState;
2578
+ if (renderState === null) {
2579
+ workInProgress.memoizedState = ({
2580
+ isBackwards: isBackwards,
2581
+ rendering: null,
2582
+ renderingStartTime: 0,
2583
+ last: lastContentRow,
2584
+ tail: tail,
2585
+ tailExpiration: 0,
2586
+ tailMode: tailMode,
2587
+ lastEffect: lastEffectBeforeRendering,
2588
+ }: SuspenseListRenderState);
2589
+ } else {
2590
+ // We can reuse the existing object from previous renders.
2591
+ renderState.isBackwards = isBackwards;
2592
+ renderState.rendering = null;
2593
+ renderState.renderingStartTime = 0;
2594
+ renderState.last = lastContentRow;
2595
+ renderState.tail = tail;
2596
+ renderState.tailExpiration = 0;
2597
+ renderState.tailMode = tailMode;
2598
+ renderState.lastEffect = lastEffectBeforeRendering;
2599
+ }
2600
+}
2601
+
2602
+// This can end up rendering this component multiple passes.
2603
+// The first pass splits the children fibers into two sets. A head and tail.
2604
+// We first render the head. If anything is in fallback state, we do another
2605
+// pass through beginWork to rerender all children (including the tail) with
2606
+// the force suspend context. If the first render didn't have anything in
2607
+// in fallback state. Then we render each row in the tail one-by-one.
2608
+// That happens in the completeWork phase without going back to beginWork.
2609
+function updateSuspenseListComponent(
2610
+ current: Fiber | null,
2611
+ workInProgress: Fiber,
2612
+ renderExpirationTime: ExpirationTime,
2613
+) {
2614
+ const nextProps = workInProgress.pendingProps;
2615
+ const revealOrder: SuspenseListRevealOrder = nextProps.revealOrder;
2616
+ const tailMode: SuspenseListTailMode = nextProps.tail;
2617
+ const newChildren = nextProps.children;
2618
+
2619
+ validateRevealOrder(revealOrder);
2620
+ validateTailOptions(tailMode, revealOrder);
2621
+ validateSuspenseListChildren(newChildren, revealOrder);
2622
+
2623
+ reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
2624
+
2625
+ let suspenseContext: SuspenseContext = suspenseStackCursor.current;
2626
+
2627
+ const shouldForceFallback = hasSuspenseContext(
2628
+ suspenseContext,
2629
+ (ForceSuspenseFallback: SuspenseContext),
2630
+ );
2631
+ if (shouldForceFallback) {
2632
+ suspenseContext = setShallowSuspenseContext(
2633
+ suspenseContext,
2634
+ ForceSuspenseFallback,
2635
+ );
2636
+ workInProgress.effectTag |= DidCapture;
2637
+ } else {
2638
+ const didSuspendBefore =
2639
+ current !== null && (current.effectTag & DidCapture) !== NoEffect;
2640
+ if (didSuspendBefore) {
2641
+ // If we previously forced a fallback, we need to schedule work
2642
+ // on any nested boundaries to let them know to try to render
2643
+ // again. This is the same as context updating.
2644
+ propagateSuspenseContextChange(
2645
+ workInProgress,
2646
+ workInProgress.child,
2647
+ renderExpirationTime,
2648
+ );
2649
+ }
2650
+ suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
2651
+ }
2652
+ pushSuspenseContext(workInProgress, suspenseContext);
2653
+
2654
+ if ((workInProgress.mode & BlockingMode) === NoMode) {
2655
+ // Outside of blocking mode, SuspenseList doesn't work so we just
2656
+ // use make it a noop by treating it as the default revealOrder.
2657
+ workInProgress.memoizedState = null;
2658
+ } else {
2659
+ switch (revealOrder) {
2660
+ case 'forwards': {
2661
+ const lastContentRow = findLastContentRow(workInProgress.child);
2662
+ let tail;
2663
+ if (lastContentRow === null) {
2664
+ // The whole list is part of the tail.
2665
+ // TODO: We could fast path by just rendering the tail now.
2666
+ tail = workInProgress.child;
2667
+ workInProgress.child = null;
2668
+ } else {
2669
+ // Disconnect the tail rows after the content row.
2670
+ // We're going to render them separately later.
2671
+ tail = lastContentRow.sibling;
2672
+ lastContentRow.sibling = null;
2673
+ }
2674
+ initSuspenseListRenderState(
2675
+ workInProgress,
2676
+ false, // isBackwards
2677
+ tail,
2678
+ lastContentRow,
2679
+ tailMode,
2680
+ workInProgress.lastEffect,
2681
+ );
2682
+ break;
2683
+ }
2684
+ case 'backwards': {
2685
+ // We're going to find the first row that has existing content.
2686
+ // At the same time we're going to reverse the list of everything
2687
+ // we pass in the meantime. That's going to be our tail in reverse
2688
+ // order.
2689
+ let tail = null;
2690
+ let row = workInProgress.child;
2691
+ workInProgress.child = null;
2692
+ while (row !== null) {
2693
+ const currentRow = row.alternate;
2694
+ // New rows can't be content rows.
2695
+ if (currentRow !== null && findFirstSuspended(currentRow) === null) {
2696
+ // This is the beginning of the main content.
2697
+ workInProgress.child = row;
2698
+ break;
2699
+ }
2700
+ const nextRow = row.sibling;
2701
+ row.sibling = tail;
2702
+ tail = row;
2703
+ row = nextRow;
2704
+ }
2705
+ // TODO: If workInProgress.child is null, we can continue on the tail immediately.
2706
+ initSuspenseListRenderState(
2707
+ workInProgress,
2708
+ true, // isBackwards
2709
+ tail,
2710
+ null, // last
2711
+ tailMode,
2712
+ workInProgress.lastEffect,
2713
+ );
2714
+ break;
2715
+ }
2716
+ case 'together': {
2717
+ initSuspenseListRenderState(
2718
+ workInProgress,
2719
+ false, // isBackwards
2720
+ null, // tail
2721
+ null, // last
2722
+ undefined,
2723
+ workInProgress.lastEffect,
2724
+ );
2725
+ break;
2726
+ }
2727
+ default: {
2728
+ // The default reveal order is the same as not having
2729
+ // a boundary.
2730
+ workInProgress.memoizedState = null;
2731
+ }
2732
+ }
2733
+ }
2734
+ return workInProgress.child;
2735
+}
2736
+
2737
+function updatePortalComponent(
2738
+ current: Fiber | null,
2739
+ workInProgress: Fiber,
2740
+ renderExpirationTime: ExpirationTime,
2741
+) {
2742
+ pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
2743
+ const nextChildren = workInProgress.pendingProps;
2744
+ if (current === null) {
2745
+ // Portals are special because we don't append the children during mount
2746
+ // but at commit. Therefore we need to track insertions which the normal
2747
+ // flow doesn't do during mount. This doesn't happen at the root because
2748
+ // the root always starts with a "current" with a null child.
2749
+ // TODO: Consider unifying this with how the root works.
2750
+ workInProgress.child = reconcileChildFibers(
2751
+ workInProgress,
2752
+ null,
2753
+ nextChildren,
2754
+ renderExpirationTime,
2755
+ );
2756
+ } else {
2757
+ reconcileChildren(
2758
+ current,
2759
+ workInProgress,
2760
+ nextChildren,
2761
+ renderExpirationTime,
2762
+ );
2763
+ }
2764
+ return workInProgress.child;
2765
+}
2766
+
2767
+function updateContextProvider(
2768
+ current: Fiber | null,
2769
+ workInProgress: Fiber,
2770
+ renderExpirationTime: ExpirationTime,
2771
+) {
2772
+ const providerType: ReactProviderType<any> = workInProgress.type;
2773
+ const context: ReactContext<any> = providerType._context;
2774
+
2775
+ const newProps = workInProgress.pendingProps;
2776
+ const oldProps = workInProgress.memoizedProps;
2777
+
2778
+ const newValue = newProps.value;
2779
+
2780
+ if (__DEV__) {
2781
+ const providerPropTypes = workInProgress.type.propTypes;
2782
+
2783
+ if (providerPropTypes) {
2784
+ checkPropTypes(providerPropTypes, newProps, 'prop', 'Context.Provider');
2785
+ }
2786
+ }
2787
+
2788
+ pushProvider(workInProgress, newValue);
2789
+
2790
+ if (oldProps !== null) {
2791
+ const oldValue = oldProps.value;
2792
+ const changedBits = calculateChangedBits(context, newValue, oldValue);
2793
+ if (changedBits === 0) {
2794
+ // No change. Bailout early if children are the same.
2795
+ if (
2796
+ oldProps.children === newProps.children &&
2797
+ !hasLegacyContextChanged()
2798
+ ) {
2799
+ return bailoutOnAlreadyFinishedWork(
2800
+ current,
2801
+ workInProgress,
2802
+ renderExpirationTime,
2803
+ );
2804
+ }
2805
+ } else {
2806
+ // The context value changed. Search for matching consumers and schedule
2807
+ // them to update.
2808
+ propagateContextChange(
2809
+ workInProgress,
2810
+ context,
2811
+ changedBits,
2812
+ renderExpirationTime,
2813
+ );
2814
+ }
2815
+ }
2816
+
2817
+ const newChildren = newProps.children;
2818
+ reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
2819
+ return workInProgress.child;
2820
+}
2821
+
2822
+let hasWarnedAboutUsingContextAsConsumer = false;
2823
+
2824
+function updateContextConsumer(
2825
+ current: Fiber | null,
2826
+ workInProgress: Fiber,
2827
+ renderExpirationTime: ExpirationTime,
2828
+) {
2829
+ let context: ReactContext<any> = workInProgress.type;
2830
+ // The logic below for Context differs depending on PROD or DEV mode. In
2831
+ // DEV mode, we create a separate object for Context.Consumer that acts
2832
+ // like a proxy to Context. This proxy object adds unnecessary code in PROD
2833
+ // so we use the old behaviour (Context.Consumer references Context) to
2834
+ // reduce size and overhead. The separate object references context via
2835
+ // a property called "_context", which also gives us the ability to check
2836
+ // in DEV mode if this property exists or not and warn if it does not.
2837
+ if (__DEV__) {
2838
+ if ((context: any)._context === undefined) {
2839
+ // This may be because it's a Context (rather than a Consumer).
2840
+ // Or it may be because it's older React where they're the same thing.
2841
+ // We only want to warn if we're sure it's a new React.
2842
+ if (context !== context.Consumer) {
2843
+ if (!hasWarnedAboutUsingContextAsConsumer) {
2844
+ hasWarnedAboutUsingContextAsConsumer = true;
2845
+ console.error(
2846
+ 'Rendering <Context> directly is not supported and will be removed in ' +
2847
+ 'a future major release. Did you mean to render <Context.Consumer> instead?',
2848
+ );
2849
+ }
2850
+ }
2851
+ } else {
2852
+ context = (context: any)._context;
2853
+ }
2854
+ }
2855
+ const newProps = workInProgress.pendingProps;
2856
+ const render = newProps.children;
2857
+
2858
+ if (__DEV__) {
2859
+ if (typeof render !== 'function') {
2860
+ console.error(
2861
+ 'A context consumer was rendered with multiple children, or a child ' +
2862
+ "that isn't a function. A context consumer expects a single child " +
2863
+ 'that is a function. If you did pass a function, make sure there ' +
2864
+ 'is no trailing or leading whitespace around it.',
2865
+ );
2866
+ }
2867
+ }
2868
+
2869
+ prepareToReadContext(workInProgress, renderExpirationTime);
2870
+ const newValue = readContext(context, newProps.unstable_observedBits);
2871
+ let newChildren;
2872
+ if (__DEV__) {
2873
+ ReactCurrentOwner.current = workInProgress;
2874
+ setIsRendering(true);
2875
+ newChildren = render(newValue);
2876
+ setIsRendering(false);
2877
+ } else {
2878
+ newChildren = render(newValue);
2879
+ }
2880
+
2881
+ // React DevTools reads this flag.
2882
+ workInProgress.effectTag |= PerformedWork;
2883
+ reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
2884
+ return workInProgress.child;
2885
+}
2886
+
2887
+function updateFundamentalComponent(
2888
+ current,
2889
+ workInProgress,
2890
+ renderExpirationTime,
2891
+) {
2892
+ const fundamentalImpl = workInProgress.type.impl;
2893
+ if (fundamentalImpl.reconcileChildren === false) {
2894
+ return null;
2895
+ }
2896
+ const nextProps = workInProgress.pendingProps;
2897
+ const nextChildren = nextProps.children;
2898
+
2899
+ reconcileChildren(
2900
+ current,
2901
+ workInProgress,
2902
+ nextChildren,
2903
+ renderExpirationTime,
2904
+ );
2905
+ return workInProgress.child;
2906
+}
2907
+
2908
+function updateScopeComponent(current, workInProgress, renderExpirationTime) {
2909
+ const nextProps = workInProgress.pendingProps;
2910
+ const nextChildren = nextProps.children;
2911
+
2912
+ reconcileChildren(
2913
+ current,
2914
+ workInProgress,
2915
+ nextChildren,
2916
+ renderExpirationTime,
2917
+ );
2918
+ return workInProgress.child;
2919
+}
2920
+
2921
+export function markWorkInProgressReceivedUpdate() {
2922
+ didReceiveUpdate = true;
2923
+}
2924
+
2925
+function bailoutOnAlreadyFinishedWork(
2926
+ current: Fiber | null,
2927
+ workInProgress: Fiber,
2928
+ renderExpirationTime: ExpirationTime,
2929
+): Fiber | null {
2930
+ if (current !== null) {
2931
+ // Reuse previous dependencies
2932
+ workInProgress.dependencies = current.dependencies;
2933
+ }
2934
+
2935
+ if (enableProfilerTimer) {
2936
+ // Don't update "base" render times for bailouts.
2937
+ stopProfilerTimerIfRunning(workInProgress);
2938
+ }
2939
+
2940
+ const updateExpirationTime = workInProgress.expirationTime;
2941
+ if (updateExpirationTime !== NoWork) {
2942
+ markUnprocessedUpdateTime(updateExpirationTime);
2943
+ }
2944
+
2945
+ // Check if the children have any pending work.
2946
+ const childExpirationTime = workInProgress.childExpirationTime;
2947
+ if (childExpirationTime < renderExpirationTime) {
2948
+ // The children don't have any work either. We can skip them.
2949
+ // TODO: Once we add back resuming, we should check if the children are
2950
+ // a work-in-progress set. If so, we need to transfer their effects.
2951
+ return null;
2952
+ } else {
2953
+ // This fiber doesn't have work, but its subtree does. Clone the child
2954
+ // fibers and continue.
2955
+ cloneChildFibers(current, workInProgress);
2956
+ return workInProgress.child;
2957
+ }
2958
+}
2959
+
2960
+function remountFiber(
2961
+ current: Fiber,
2962
+ oldWorkInProgress: Fiber,
2963
+ newWorkInProgress: Fiber,
2964
+): Fiber | null {
2965
+ if (__DEV__) {
2966
+ const returnFiber = oldWorkInProgress.return;
2967
+ if (returnFiber === null) {
2968
+ throw new Error('Cannot swap the root fiber.');
2969
+ }
2970
+
2971
+ // Disconnect from the old current.
2972
+ // It will get deleted.
2973
+ current.alternate = null;
2974
+ oldWorkInProgress.alternate = null;
2975
+
2976
+ // Connect to the new tree.
2977
+ newWorkInProgress.index = oldWorkInProgress.index;
2978
+ newWorkInProgress.sibling = oldWorkInProgress.sibling;
2979
+ newWorkInProgress.return = oldWorkInProgress.return;
2980
+ newWorkInProgress.ref = oldWorkInProgress.ref;
2981
+
2982
+ // Replace the child/sibling pointers above it.
2983
+ if (oldWorkInProgress === returnFiber.child) {
2984
+ returnFiber.child = newWorkInProgress;
2985
+ } else {
2986
+ let prevSibling = returnFiber.child;
2987
+ if (prevSibling === null) {
2988
+ throw new Error('Expected parent to have a child.');
2989
+ }
2990
+ while (prevSibling.sibling !== oldWorkInProgress) {
2991
+ prevSibling = prevSibling.sibling;
2992
+ if (prevSibling === null) {
2993
+ throw new Error('Expected to find the previous sibling.');
2994
+ }
2995
+ }
2996
+ prevSibling.sibling = newWorkInProgress;
2997
+ }
2998
+
2999
+ // Delete the old fiber and place the new one.
3000
+ // Since the old fiber is disconnected, we have to schedule it manually.
3001
+ const last = returnFiber.lastEffect;
3002
+ if (last !== null) {
3003
+ last.nextEffect = current;
3004
+ returnFiber.lastEffect = current;
3005
+ } else {
3006
+ returnFiber.firstEffect = returnFiber.lastEffect = current;
3007
+ }
3008
+ current.nextEffect = null;
3009
+ current.effectTag = Deletion;
3010
+
3011
+ newWorkInProgress.effectTag |= Placement;
3012
+
3013
+ // Restart work from the new fiber.
3014
+ return newWorkInProgress;
3015
+ } else {
3016
+ throw new Error(
3017
+ 'Did not expect this call in production. ' +
3018
+ 'This is a bug in React. Please file an issue.',
3019
+ );
3020
+ }
3021
+}
3022
+
3023
+function beginWork(
3024
+ current: Fiber | null,
3025
+ workInProgress: Fiber,
3026
+ renderExpirationTime: ExpirationTime,
3027
+): Fiber | null {
3028
+ const updateExpirationTime = workInProgress.expirationTime;
3029
+
3030
+ if (__DEV__) {
3031
+ if (workInProgress._debugNeedsRemount && current !== null) {
3032
+ // This will restart the begin phase with a new fiber.
3033
+ return remountFiber(
3034
+ current,
3035
+ workInProgress,
3036
+ createFiberFromTypeAndProps(
3037
+ workInProgress.type,
3038
+ workInProgress.key,
3039
+ workInProgress.pendingProps,
3040
+ workInProgress._debugOwner || null,
3041
+ workInProgress.mode,
3042
+ workInProgress.expirationTime,
3043
+ ),
3044
+ );
3045
+ }
3046
+ }
3047
+
3048
+ if (current !== null) {
3049
+ const oldProps = current.memoizedProps;
3050
+ const newProps = workInProgress.pendingProps;
3051
+
3052
+ if (
3053
+ oldProps !== newProps ||
3054
+ hasLegacyContextChanged() ||
3055
+ // Force a re-render if the implementation changed due to hot reload:
3056
+ (__DEV__ ? workInProgress.type !== current.type : false)
3057
+ ) {
3058
+ // If props or context changed, mark the fiber as having performed work.
3059
+ // This may be unset if the props are determined to be equal later (memo).
3060
+ didReceiveUpdate = true;
3061
+ } else if (updateExpirationTime < renderExpirationTime) {
3062
+ didReceiveUpdate = false;
3063
+ // This fiber does not have any pending work. Bailout without entering
3064
+ // the begin phase. There's still some bookkeeping we that needs to be done
3065
+ // in this optimized path, mostly pushing stuff onto the stack.
3066
+ switch (workInProgress.tag) {
3067
+ case HostRoot:
3068
+ pushHostRootContext(workInProgress);
3069
+ resetHydrationState();
3070
+ break;
3071
+ case HostComponent:
3072
+ pushHostContext(workInProgress);
3073
+ if (
3074
+ workInProgress.mode & ConcurrentMode &&
3075
+ renderExpirationTime !== Never &&
3076
+ shouldDeprioritizeSubtree(workInProgress.type, newProps)
3077
+ ) {
3078
+ if (enableSchedulerTracing) {
3079
+ markSpawnedWork(Never);
3080
+ }
3081
+ // Schedule this fiber to re-render at offscreen priority. Then bailout.
3082
+ workInProgress.expirationTime = workInProgress.childExpirationTime = Never;
3083
+ return null;
3084
+ }
3085
+ break;
3086
+ case ClassComponent: {
3087
+ const Component = workInProgress.type;
3088
+ if (isLegacyContextProvider(Component)) {
3089
+ pushLegacyContextProvider(workInProgress);
3090
+ }
3091
+ break;
3092
+ }
3093
+ case HostPortal:
3094
+ pushHostContainer(
3095
+ workInProgress,
3096
+ workInProgress.stateNode.containerInfo,
3097
+ );
3098
+ break;
3099
+ case ContextProvider: {
3100
+ const newValue = workInProgress.memoizedProps.value;
3101
+ pushProvider(workInProgress, newValue);
3102
+ break;
3103
+ }
3104
+ case Profiler:
3105
+ if (enableProfilerTimer) {
3106
+ // Profiler should only call onRender when one of its descendants actually rendered.
3107
+ const hasChildWork =
3108
+ workInProgress.childExpirationTime >= renderExpirationTime;
3109
+ if (hasChildWork) {
3110
+ workInProgress.effectTag |= Update;
3111
+ }
3112
+
3113
+ // Reset effect durations for the next eventual effect phase.
3114
+ // These are reset during render to allow the DevTools commit hook a chance to read them,
3115
+ const stateNode = workInProgress.stateNode;
3116
+ stateNode.effectDuration = 0;
3117
+ stateNode.passiveEffectDuration = 0;
3118
+ }
3119
+ break;
3120
+ case SuspenseComponent: {
3121
+ const state: SuspenseState | null = workInProgress.memoizedState;
3122
+ if (state !== null) {
3123
+ if (enableSuspenseServerRenderer) {
3124
+ if (state.dehydrated !== null) {
3125
+ pushSuspenseContext(
3126
+ workInProgress,
3127
+ setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3128
+ );
3129
+ // We know that this component will suspend again because if it has
3130
+ // been unsuspended it has committed as a resolved Suspense component.
3131
+ // If it needs to be retried, it should have work scheduled on it.
3132
+ workInProgress.effectTag |= DidCapture;
3133
+ break;
3134
+ }
3135
+ }
3136
+
3137
+ // If this boundary is currently timed out, we need to decide
3138
+ // whether to retry the primary children, or to skip over it and
3139
+ // go straight to the fallback. Check the priority of the primary
3140
+ // child fragment.
3141
+ const primaryChildFragment: Fiber = (workInProgress.child: any);
3142
+ const primaryChildExpirationTime =
3143
+ primaryChildFragment.childExpirationTime;
3144
+ if (
3145
+ primaryChildExpirationTime !== NoWork &&
3146
+ primaryChildExpirationTime >= renderExpirationTime
3147
+ ) {
3148
+ // The primary children have pending work. Use the normal path
3149
+ // to attempt to render the primary children again.
3150
+ return updateSuspenseComponent(
3151
+ current,
3152
+ workInProgress,
3153
+ renderExpirationTime,
3154
+ );
3155
+ } else {
3156
+ // The primary child fragment does not have pending work marked
3157
+ // on it...
3158
+
3159
+ // ...usually. There's an unfortunate edge case where the fragment
3160
+ // fiber is not part of the return path of the children, so when
3161
+ // an update happens, the fragment doesn't get marked during
3162
+ // setState. This is something we should consider addressing when
3163
+ // we refactor the Fiber data structure. (There's a test with more
3164
+ // details; to find it, comment out the following block and see
3165
+ // which one fails.)
3166
+ //
3167
+ // As a workaround, we need to recompute the `childExpirationTime`
3168
+ // by bubbling it up from the next level of children. This is
3169
+ // based on similar logic in `resetChildExpirationTime`.
3170
+ let primaryChild = primaryChildFragment.child;
3171
+ while (primaryChild !== null) {
3172
+ const childUpdateExpirationTime = primaryChild.expirationTime;
3173
+ const childChildExpirationTime =
3174
+ primaryChild.childExpirationTime;
3175
+ if (
3176
+ (childUpdateExpirationTime !== NoWork &&
3177
+ childUpdateExpirationTime >= renderExpirationTime) ||
3178
+ (childChildExpirationTime !== NoWork &&
3179
+ childChildExpirationTime >= renderExpirationTime)
3180
+ ) {
3181
+ // Found a child with an update with sufficient priority.
3182
+ // Use the normal path to render the primary children again.
3183
+ return updateSuspenseComponent(
3184
+ current,
3185
+ workInProgress,
3186
+ renderExpirationTime,
3187
+ );
3188
+ }
3189
+ primaryChild = primaryChild.sibling;
3190
+ }
3191
+
3192
+ pushSuspenseContext(
3193
+ workInProgress,
3194
+ setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3195
+ );
3196
+ // The primary children do not have pending work with sufficient
3197
+ // priority. Bailout.
3198
+ const child = bailoutOnAlreadyFinishedWork(
3199
+ current,
3200
+ workInProgress,
3201
+ renderExpirationTime,
3202
+ );
3203
+ if (child !== null) {
3204
+ // The fallback children have pending work. Skip over the
3205
+ // primary children and work on the fallback.
3206
+ return child.sibling;
3207
+ } else {
3208
+ return null;
3209
+ }
3210
+ }
3211
+ } else {
3212
+ pushSuspenseContext(
3213
+ workInProgress,
3214
+ setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3215
+ );
3216
+ }
3217
+ break;
3218
+ }
3219
+ case SuspenseListComponent: {
3220
+ const didSuspendBefore =
3221
+ (current.effectTag & DidCapture) !== NoEffect;
3222
+
3223
+ const hasChildWork =
3224
+ workInProgress.childExpirationTime >= renderExpirationTime;
3225
+
3226
+ if (didSuspendBefore) {
3227
+ if (hasChildWork) {
3228
+ // If something was in fallback state last time, and we have all the
3229
+ // same children then we're still in progressive loading state.
3230
+ // Something might get unblocked by state updates or retries in the
3231
+ // tree which will affect the tail. So we need to use the normal
3232
+ // path to compute the correct tail.
3233
+ return updateSuspenseListComponent(
3234
+ current,
3235
+ workInProgress,
3236
+ renderExpirationTime,
3237
+ );
3238
+ }
3239
+ // If none of the children had any work, that means that none of
3240
+ // them got retried so they'll still be blocked in the same way
3241
+ // as before. We can fast bail out.
3242
+ workInProgress.effectTag |= DidCapture;
3243
+ }
3244
+
3245
+ // If nothing suspended before and we're rendering the same children,
3246
+ // then the tail doesn't matter. Anything new that suspends will work
3247
+ // in the "together" mode, so we can continue from the state we had.
3248
+ const renderState = workInProgress.memoizedState;
3249
+ if (renderState !== null) {
3250
+ // Reset to the "together" mode in case we've started a different
3251
+ // update in the past but didn't complete it.
3252
+ renderState.rendering = null;
3253
+ renderState.tail = null;
3254
+ renderState.lastEffect = null;
3255
+ }
3256
+ pushSuspenseContext(workInProgress, suspenseStackCursor.current);
3257
+
3258
+ if (hasChildWork) {
3259
+ break;
3260
+ } else {
3261
+ // If none of the children had any work, that means that none of
3262
+ // them got retried so they'll still be blocked in the same way
3263
+ // as before. We can fast bail out.
3264
+ return null;
3265
+ }
3266
+ }
3267
+ }
3268
+ return bailoutOnAlreadyFinishedWork(
3269
+ current,
3270
+ workInProgress,
3271
+ renderExpirationTime,
3272
+ );
3273
+ } else {
3274
+ // An update was scheduled on this fiber, but there are no new props
3275
+ // nor legacy context. Set this to false. If an update queue or context
3276
+ // consumer produces a changed value, it will set this to true. Otherwise,
3277
+ // the component will assume the children have not changed and bail out.
3278
+ didReceiveUpdate = false;
3279
+ }
3280
+ } else {
3281
+ didReceiveUpdate = false;
3282
+ }
3283
+
3284
+ // Before entering the begin phase, clear pending update priority.
3285
+ // TODO: This assumes that we're about to evaluate the component and process
3286
+ // the update queue. However, there's an exception: SimpleMemoComponent
3287
+ // sometimes bails out later in the begin phase. This indicates that we should
3288
+ // move this assignment out of the common path and into each branch.
3289
+ workInProgress.expirationTime = NoWork;
3290
+
3291
+ switch (workInProgress.tag) {
3292
+ case IndeterminateComponent: {
3293
+ return mountIndeterminateComponent(
3294
+ current,
3295
+ workInProgress,
3296
+ workInProgress.type,
3297
+ renderExpirationTime,
3298
+ );
3299
+ }
3300
+ case LazyComponent: {
3301
+ const elementType = workInProgress.elementType;
3302
+ return mountLazyComponent(
3303
+ current,
3304
+ workInProgress,
3305
+ elementType,
3306
+ updateExpirationTime,
3307
+ renderExpirationTime,
3308
+ );
3309
+ }
3310
+ case FunctionComponent: {
3311
+ const Component = workInProgress.type;
3312
+ const unresolvedProps = workInProgress.pendingProps;
3313
+ const resolvedProps =
3314
+ workInProgress.elementType === Component
3315
+ ? unresolvedProps
3316
+ : resolveDefaultProps(Component, unresolvedProps);
3317
+ return updateFunctionComponent(
3318
+ current,
3319
+ workInProgress,
3320
+ Component,
3321
+ resolvedProps,
3322
+ renderExpirationTime,
3323
+ );
3324
+ }
3325
+ case ClassComponent: {
3326
+ const Component = workInProgress.type;
3327
+ const unresolvedProps = workInProgress.pendingProps;
3328
+ const resolvedProps =
3329
+ workInProgress.elementType === Component
3330
+ ? unresolvedProps
3331
+ : resolveDefaultProps(Component, unresolvedProps);
3332
+ return updateClassComponent(
3333
+ current,
3334
+ workInProgress,
3335
+ Component,
3336
+ resolvedProps,
3337
+ renderExpirationTime,
3338
+ );
3339
+ }
3340
+ case HostRoot:
3341
+ return updateHostRoot(current, workInProgress, renderExpirationTime);
3342
+ case HostComponent:
3343
+ return updateHostComponent(current, workInProgress, renderExpirationTime);
3344
+ case HostText:
3345
+ return updateHostText(current, workInProgress);
3346
+ case SuspenseComponent:
3347
+ return updateSuspenseComponent(
3348
+ current,
3349
+ workInProgress,
3350
+ renderExpirationTime,
3351
+ );
3352
+ case HostPortal:
3353
+ return updatePortalComponent(
3354
+ current,
3355
+ workInProgress,
3356
+ renderExpirationTime,
3357
+ );
3358
+ case ForwardRef: {
3359
+ const type = workInProgress.type;
3360
+ const unresolvedProps = workInProgress.pendingProps;
3361
+ const resolvedProps =
3362
+ workInProgress.elementType === type
3363
+ ? unresolvedProps
3364
+ : resolveDefaultProps(type, unresolvedProps);
3365
+ return updateForwardRef(
3366
+ current,
3367
+ workInProgress,
3368
+ type,
3369
+ resolvedProps,
3370
+ renderExpirationTime,
3371
+ );
3372
+ }
3373
+ case Fragment:
3374
+ return updateFragment(current, workInProgress, renderExpirationTime);
3375
+ case Mode:
3376
+ return updateMode(current, workInProgress, renderExpirationTime);
3377
+ case Profiler:
3378
+ return updateProfiler(current, workInProgress, renderExpirationTime);
3379
+ case ContextProvider:
3380
+ return updateContextProvider(
3381
+ current,
3382
+ workInProgress,
3383
+ renderExpirationTime,
3384
+ );
3385
+ case ContextConsumer:
3386
+ return updateContextConsumer(
3387
+ current,
3388
+ workInProgress,
3389
+ renderExpirationTime,
3390
+ );
3391
+ case MemoComponent: {
3392
+ const type = workInProgress.type;
3393
+ const unresolvedProps = workInProgress.pendingProps;
3394
+ // Resolve outer props first, then resolve inner props.
3395
+ let resolvedProps = resolveDefaultProps(type, unresolvedProps);
3396
+ if (__DEV__) {
3397
+ if (workInProgress.type !== workInProgress.elementType) {
3398
+ const outerPropTypes = type.propTypes;
3399
+ if (outerPropTypes) {
3400
+ checkPropTypes(
3401
+ outerPropTypes,
3402
+ resolvedProps, // Resolved for outer only
3403
+ 'prop',
3404
+ getComponentName(type),
3405
+ );
3406
+ }
3407
+ }
3408
+ }
3409
+ resolvedProps = resolveDefaultProps(type.type, resolvedProps);
3410
+ return updateMemoComponent(
3411
+ current,
3412
+ workInProgress,
3413
+ type,
3414
+ resolvedProps,
3415
+ updateExpirationTime,
3416
+ renderExpirationTime,
3417
+ );
3418
+ }
3419
+ case SimpleMemoComponent: {
3420
+ return updateSimpleMemoComponent(
3421
+ current,
3422
+ workInProgress,
3423
+ workInProgress.type,
3424
+ workInProgress.pendingProps,
3425
+ updateExpirationTime,
3426
+ renderExpirationTime,
3427
+ );
3428
+ }
3429
+ case IncompleteClassComponent: {
3430
+ const Component = workInProgress.type;
3431
+ const unresolvedProps = workInProgress.pendingProps;
3432
+ const resolvedProps =
3433
+ workInProgress.elementType === Component
3434
+ ? unresolvedProps
3435
+ : resolveDefaultProps(Component, unresolvedProps);
3436
+ return mountIncompleteClassComponent(
3437
+ current,
3438
+ workInProgress,
3439
+ Component,
3440
+ resolvedProps,
3441
+ renderExpirationTime,
3442
+ );
3443
+ }
3444
+ case SuspenseListComponent: {
3445
+ return updateSuspenseListComponent(
3446
+ current,
3447
+ workInProgress,
3448
+ renderExpirationTime,
3449
+ );
3450
+ }
3451
+ case FundamentalComponent: {
3452
+ if (enableFundamentalAPI) {
3453
+ return updateFundamentalComponent(
3454
+ current,
3455
+ workInProgress,
3456
+ renderExpirationTime,
3457
+ );
3458
+ }
3459
+ break;
3460
+ }
3461
+ case ScopeComponent: {
3462
+ if (enableScopeAPI) {
3463
+ return updateScopeComponent(
3464
+ current,
3465
+ workInProgress,
3466
+ renderExpirationTime,
3467
+ );
3468
+ }
3469
+ break;
3470
+ }
3471
+ case Block: {
3472
+ if (enableBlocksAPI) {
3473
+ const block = workInProgress.type;
3474
+ const props = workInProgress.pendingProps;
3475
+ return updateBlock(
3476
+ current,
3477
+ workInProgress,
3478
+ block,
3479
+ props,
3480
+ renderExpirationTime,
3481
+ );
3482
+ }
3483
+ break;
3484
+ }
3485
+ }
3486
+ invariant(
3487
+ false,
3488
+ 'Unknown unit of work tag (%s). This error is likely caused by a bug in ' +
3489
+ 'React. Please file an issue.',
3490
+ workInProgress.tag,
3491
+ );
3492
+}
3493
+
3494
+export {beginWork};
packages/react-reconciler/src/ReactFiberClassComponent.new.js
new
+1182
@@ -0,0 +1,1182 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactInternalTypes';
11
+import type {ExpirationTime} from './ReactFiberExpirationTime';
12
+import type {UpdateQueue} from './ReactUpdateQueue.new';
13
+
14
+import * as React from 'react';
15
+import {Update, Snapshot} from './ReactSideEffectTags';
16
+import {
17
+ debugRenderPhaseSideEffectsForStrictMode,
18
+ disableLegacyContext,
19
+ warnAboutDeprecatedLifecycles,
20
+} from 'shared/ReactFeatureFlags';
21
+import ReactStrictModeWarnings from './ReactStrictModeWarnings.new';
22
+import {isMounted} from './ReactFiberTreeReflection';
23
+import {get as getInstance, set as setInstance} from 'shared/ReactInstanceMap';
24
+import shallowEqual from 'shared/shallowEqual';
25
+import getComponentName from 'shared/getComponentName';
26
+import invariant from 'shared/invariant';
27
+import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
28
+
29
+import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
30
+import {StrictMode} from './ReactTypeOfMode';
31
+
32
+import {
33
+ enqueueUpdate,
34
+ processUpdateQueue,
35
+ checkHasForceUpdateAfterProcessing,
36
+ resetHasForceUpdateBeforeProcessing,
37
+ createUpdate,
38
+ ReplaceState,
39
+ ForceUpdate,
40
+ initializeUpdateQueue,
41
+ cloneUpdateQueue,
42
+} from './ReactUpdateQueue.new';
43
+import {NoWork} from './ReactFiberExpirationTime';
44
+import {
45
+ cacheContext,
46
+ getMaskedContext,
47
+ getUnmaskedContext,
48
+ hasContextChanged,
49
+ emptyContextObject,
50
+} from './ReactFiberContext.new';
51
+import {readContext} from './ReactFiberNewContext.new';
52
+import {
53
+ requestCurrentTimeForUpdate,
54
+ computeExpirationForFiber,
55
+ scheduleUpdateOnFiber,
56
+} from './ReactFiberWorkLoop.new';
57
+import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
58
+
59
+import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
60
+
61
+const fakeInternalInstance = {};
62
+const isArray = Array.isArray;
63
+
64
+// React.Component uses a shared frozen object by default.
65
+// We'll use it to determine whether we need to initialize legacy refs.
66
+export const emptyRefsObject = new React.Component().refs;
67
+
68
+let didWarnAboutStateAssignmentForComponent;
69
+let didWarnAboutUninitializedState;
70
+let didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate;
71
+let didWarnAboutLegacyLifecyclesAndDerivedState;
72
+let didWarnAboutUndefinedDerivedState;
73
+let warnOnUndefinedDerivedState;
74
+let warnOnInvalidCallback;
75
+let didWarnAboutDirectlyAssigningPropsToState;
76
+let didWarnAboutContextTypeAndContextTypes;
77
+let didWarnAboutInvalidateContextType;
78
+
79
+if (__DEV__) {
80
+ didWarnAboutStateAssignmentForComponent = new Set();
81
+ didWarnAboutUninitializedState = new Set();
82
+ didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate = new Set();
83
+ didWarnAboutLegacyLifecyclesAndDerivedState = new Set();
84
+ didWarnAboutDirectlyAssigningPropsToState = new Set();
85
+ didWarnAboutUndefinedDerivedState = new Set();
86
+ didWarnAboutContextTypeAndContextTypes = new Set();
87
+ didWarnAboutInvalidateContextType = new Set();
88
+
89
+ const didWarnOnInvalidCallback = new Set();
90
+
91
+ warnOnInvalidCallback = function(callback: mixed, callerName: string) {
92
+ if (callback === null || typeof callback === 'function') {
93
+ return;
94
+ }
95
+ const key = `${callerName}_${(callback: any)}`;
96
+ if (!didWarnOnInvalidCallback.has(key)) {
97
+ didWarnOnInvalidCallback.add(key);
98
+ console.error(
99
+ '%s(...): Expected the last optional `callback` argument to be a ' +
100
+ 'function. Instead received: %s.',
101
+ callerName,
102
+ callback,
103
+ );
104
+ }
105
+ };
106
+
107
+ warnOnUndefinedDerivedState = function(type, partialState) {
108
+ if (partialState === undefined) {
109
+ const componentName = getComponentName(type) || 'Component';
110
+ if (!didWarnAboutUndefinedDerivedState.has(componentName)) {
111
+ didWarnAboutUndefinedDerivedState.add(componentName);
112
+ console.error(
113
+ '%s.getDerivedStateFromProps(): A valid state object (or null) must be returned. ' +
114
+ 'You have returned undefined.',
115
+ componentName,
116
+ );
117
+ }
118
+ }
119
+ };
120
+
121
+ // This is so gross but it's at least non-critical and can be removed if
122
+ // it causes problems. This is meant to give a nicer error message for
123
+ // ReactDOM15.unstable_renderSubtreeIntoContainer(reactDOM16Component,
124
+ // ...)) which otherwise throws a "_processChildContext is not a function"
125
+ // exception.
126
+ Object.defineProperty(fakeInternalInstance, '_processChildContext', {
127
+ enumerable: false,
128
+ value: function() {
129
+ invariant(
130
+ false,
131
+ '_processChildContext is not available in React 16+. This likely ' +
132
+ 'means you have multiple copies of React and are attempting to nest ' +
133
+ 'a React 15 tree inside a React 16 tree using ' +
134
+ "unstable_renderSubtreeIntoContainer, which isn't supported. Try " +
135
+ 'to make sure you have only one copy of React (and ideally, switch ' +
136
+ 'to ReactDOM.createPortal).',
137
+ );
138
+ },
139
+ });
140
+ Object.freeze(fakeInternalInstance);
141
+}
142
+
143
+export function applyDerivedStateFromProps(
144
+ workInProgress: Fiber,
145
+ ctor: any,
146
+ getDerivedStateFromProps: (props: any, state: any) => any,
147
+ nextProps: any,
148
+) {
149
+ const prevState = workInProgress.memoizedState;
150
+
151
+ if (__DEV__) {
152
+ if (
153
+ debugRenderPhaseSideEffectsForStrictMode &&
154
+ workInProgress.mode & StrictMode
155
+ ) {
156
+ disableLogs();
157
+ try {
158
+ // Invoke the function an extra time to help detect side-effects.
159
+ getDerivedStateFromProps(nextProps, prevState);
160
+ } finally {
161
+ reenableLogs();
162
+ }
163
+ }
164
+ }
165
+
166
+ const partialState = getDerivedStateFromProps(nextProps, prevState);
167
+
168
+ if (__DEV__) {
169
+ warnOnUndefinedDerivedState(ctor, partialState);
170
+ }
171
+ // Merge the partial state and the previous state.
172
+ const memoizedState =
173
+ partialState === null || partialState === undefined
174
+ ? prevState
175
+ : Object.assign({}, prevState, partialState);
176
+ workInProgress.memoizedState = memoizedState;
177
+
178
+ // Once the update queue is empty, persist the derived state onto the
179
+ // base state.
180
+ if (workInProgress.expirationTime === NoWork) {
181
+ // Queue is always non-null for classes
182
+ const updateQueue: UpdateQueue<any> = (workInProgress.updateQueue: any);
183
+ updateQueue.baseState = memoizedState;
184
+ }
185
+}
186
+
187
+const classComponentUpdater = {
188
+ isMounted,
189
+ enqueueSetState(inst, payload, callback) {
190
+ const fiber = getInstance(inst);
191
+ const currentTime = requestCurrentTimeForUpdate();
192
+ const suspenseConfig = requestCurrentSuspenseConfig();
193
+ const expirationTime = computeExpirationForFiber(
194
+ currentTime,
195
+ fiber,
196
+ suspenseConfig,
197
+ );
198
+
199
+ const update = createUpdate(expirationTime, suspenseConfig);
200
+ update.payload = payload;
201
+ if (callback !== undefined && callback !== null) {
202
+ if (__DEV__) {
203
+ warnOnInvalidCallback(callback, 'setState');
204
+ }
205
+ update.callback = callback;
206
+ }
207
+
208
+ enqueueUpdate(fiber, update);
209
+ scheduleUpdateOnFiber(fiber, expirationTime);
210
+ },
211
+ enqueueReplaceState(inst, payload, callback) {
212
+ const fiber = getInstance(inst);
213
+ const currentTime = requestCurrentTimeForUpdate();
214
+ const suspenseConfig = requestCurrentSuspenseConfig();
215
+ const expirationTime = computeExpirationForFiber(
216
+ currentTime,
217
+ fiber,
218
+ suspenseConfig,
219
+ );
220
+
221
+ const update = createUpdate(expirationTime, suspenseConfig);
222
+ update.tag = ReplaceState;
223
+ update.payload = payload;
224
+
225
+ if (callback !== undefined && callback !== null) {
226
+ if (__DEV__) {
227
+ warnOnInvalidCallback(callback, 'replaceState');
228
+ }
229
+ update.callback = callback;
230
+ }
231
+
232
+ enqueueUpdate(fiber, update);
233
+ scheduleUpdateOnFiber(fiber, expirationTime);
234
+ },
235
+ enqueueForceUpdate(inst, callback) {
236
+ const fiber = getInstance(inst);
237
+ const currentTime = requestCurrentTimeForUpdate();
238
+ const suspenseConfig = requestCurrentSuspenseConfig();
239
+ const expirationTime = computeExpirationForFiber(
240
+ currentTime,
241
+ fiber,
242
+ suspenseConfig,
243
+ );
244
+
245
+ const update = createUpdate(expirationTime, suspenseConfig);
246
+ update.tag = ForceUpdate;
247
+
248
+ if (callback !== undefined && callback !== null) {
249
+ if (__DEV__) {
250
+ warnOnInvalidCallback(callback, 'forceUpdate');
251
+ }
252
+ update.callback = callback;
253
+ }
254
+
255
+ enqueueUpdate(fiber, update);
256
+ scheduleUpdateOnFiber(fiber, expirationTime);
257
+ },
258
+};
259
+
260
+function checkShouldComponentUpdate(
261
+ workInProgress,
262
+ ctor,
263
+ oldProps,
264
+ newProps,
265
+ oldState,
266
+ newState,
267
+ nextContext,
268
+) {
269
+ const instance = workInProgress.stateNode;
270
+ if (typeof instance.shouldComponentUpdate === 'function') {
271
+ if (__DEV__) {
272
+ if (
273
+ debugRenderPhaseSideEffectsForStrictMode &&
274
+ workInProgress.mode & StrictMode
275
+ ) {
276
+ disableLogs();
277
+ try {
278
+ // Invoke the function an extra time to help detect side-effects.
279
+ instance.shouldComponentUpdate(newProps, newState, nextContext);
280
+ } finally {
281
+ reenableLogs();
282
+ }
283
+ }
284
+ }
285
+ const shouldUpdate = instance.shouldComponentUpdate(
286
+ newProps,
287
+ newState,
288
+ nextContext,
289
+ );
290
+
291
+ if (__DEV__) {
292
+ if (shouldUpdate === undefined) {
293
+ console.error(
294
+ '%s.shouldComponentUpdate(): Returned undefined instead of a ' +
295
+ 'boolean value. Make sure to return true or false.',
296
+ getComponentName(ctor) || 'Component',
297
+ );
298
+ }
299
+ }
300
+
301
+ return shouldUpdate;
302
+ }
303
+
304
+ if (ctor.prototype && ctor.prototype.isPureReactComponent) {
305
+ return (
306
+ !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
307
+ );
308
+ }
309
+
310
+ return true;
311
+}
312
+
313
+function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) {
314
+ const instance = workInProgress.stateNode;
315
+ if (__DEV__) {
316
+ const name = getComponentName(ctor) || 'Component';
317
+ const renderPresent = instance.render;
318
+
319
+ if (!renderPresent) {
320
+ if (ctor.prototype && typeof ctor.prototype.render === 'function') {
321
+ console.error(
322
+ '%s(...): No `render` method found on the returned component ' +
323
+ 'instance: did you accidentally return an object from the constructor?',
324
+ name,
325
+ );
326
+ } else {
327
+ console.error(
328
+ '%s(...): No `render` method found on the returned component ' +
329
+ 'instance: you may have forgotten to define `render`.',
330
+ name,
331
+ );
332
+ }
333
+ }
334
+
335
+ if (
336
+ instance.getInitialState &&
337
+ !instance.getInitialState.isReactClassApproved &&
338
+ !instance.state
339
+ ) {
340
+ console.error(
341
+ 'getInitialState was defined on %s, a plain JavaScript class. ' +
342
+ 'This is only supported for classes created using React.createClass. ' +
343
+ 'Did you mean to define a state property instead?',
344
+ name,
345
+ );
346
+ }
347
+ if (
348
+ instance.getDefaultProps &&
349
+ !instance.getDefaultProps.isReactClassApproved
350
+ ) {
351
+ console.error(
352
+ 'getDefaultProps was defined on %s, a plain JavaScript class. ' +
353
+ 'This is only supported for classes created using React.createClass. ' +
354
+ 'Use a static property to define defaultProps instead.',
355
+ name,
356
+ );
357
+ }
358
+ if (instance.propTypes) {
359
+ console.error(
360
+ 'propTypes was defined as an instance property on %s. Use a static ' +
361
+ 'property to define propTypes instead.',
362
+ name,
363
+ );
364
+ }
365
+ if (instance.contextType) {
366
+ console.error(
367
+ 'contextType was defined as an instance property on %s. Use a static ' +
368
+ 'property to define contextType instead.',
369
+ name,
370
+ );
371
+ }
372
+
373
+ if (disableLegacyContext) {
374
+ if (ctor.childContextTypes) {
375
+ console.error(
376
+ '%s uses the legacy childContextTypes API which is no longer supported. ' +
377
+ 'Use React.createContext() instead.',
378
+ name,
379
+ );
380
+ }
381
+ if (ctor.contextTypes) {
382
+ console.error(
383
+ '%s uses the legacy contextTypes API which is no longer supported. ' +
384
+ 'Use React.createContext() with static contextType instead.',
385
+ name,
386
+ );
387
+ }
388
+ } else {
389
+ if (instance.contextTypes) {
390
+ console.error(
391
+ 'contextTypes was defined as an instance property on %s. Use a static ' +
392
+ 'property to define contextTypes instead.',
393
+ name,
394
+ );
395
+ }
396
+
397
+ if (
398
+ ctor.contextType &&
399
+ ctor.contextTypes &&
400
+ !didWarnAboutContextTypeAndContextTypes.has(ctor)
401
+ ) {
402
+ didWarnAboutContextTypeAndContextTypes.add(ctor);
403
+ console.error(
404
+ '%s declares both contextTypes and contextType static properties. ' +
405
+ 'The legacy contextTypes property will be ignored.',
406
+ name,
407
+ );
408
+ }
409
+ }
410
+
411
+ if (typeof instance.componentShouldUpdate === 'function') {
412
+ console.error(
413
+ '%s has a method called ' +
414
+ 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
415
+ 'The name is phrased as a question because the function is ' +
416
+ 'expected to return a value.',
417
+ name,
418
+ );
419
+ }
420
+ if (
421
+ ctor.prototype &&
422
+ ctor.prototype.isPureReactComponent &&
423
+ typeof instance.shouldComponentUpdate !== 'undefined'
424
+ ) {
425
+ console.error(
426
+ '%s has a method called shouldComponentUpdate(). ' +
427
+ 'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
428
+ 'Please extend React.Component if shouldComponentUpdate is used.',
429
+ getComponentName(ctor) || 'A pure component',
430
+ );
431
+ }
432
+ if (typeof instance.componentDidUnmount === 'function') {
433
+ console.error(
434
+ '%s has a method called ' +
435
+ 'componentDidUnmount(). But there is no such lifecycle method. ' +
436
+ 'Did you mean componentWillUnmount()?',
437
+ name,
438
+ );
439
+ }
440
+ if (typeof instance.componentDidReceiveProps === 'function') {
441
+ console.error(
442
+ '%s has a method called ' +
443
+ 'componentDidReceiveProps(). But there is no such lifecycle method. ' +
444
+ 'If you meant to update the state in response to changing props, ' +
445
+ 'use componentWillReceiveProps(). If you meant to fetch data or ' +
446
+ 'run side-effects or mutations after React has updated the UI, use componentDidUpdate().',
447
+ name,
448
+ );
449
+ }
450
+ if (typeof instance.componentWillRecieveProps === 'function') {
451
+ console.error(
452
+ '%s has a method called ' +
453
+ 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
454
+ name,
455
+ );
456
+ }
457
+ if (typeof instance.UNSAFE_componentWillRecieveProps === 'function') {
458
+ console.error(
459
+ '%s has a method called ' +
460
+ 'UNSAFE_componentWillRecieveProps(). Did you mean UNSAFE_componentWillReceiveProps()?',
461
+ name,
462
+ );
463
+ }
464
+ const hasMutatedProps = instance.props !== newProps;
465
+ if (instance.props !== undefined && hasMutatedProps) {
466
+ console.error(
467
+ '%s(...): When calling super() in `%s`, make sure to pass ' +
468
+ "up the same props that your component's constructor was passed.",
469
+ name,
470
+ name,
471
+ );
472
+ }
473
+ if (instance.defaultProps) {
474
+ console.error(
475
+ 'Setting defaultProps as an instance property on %s is not supported and will be ignored.' +
476
+ ' Instead, define defaultProps as a static property on %s.',
477
+ name,
478
+ name,
479
+ );
480
+ }
481
+
482
+ if (
483
+ typeof instance.getSnapshotBeforeUpdate === 'function' &&
484
+ typeof instance.componentDidUpdate !== 'function' &&
485
+ !didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.has(ctor)
486
+ ) {
487
+ didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.add(ctor);
488
+ console.error(
489
+ '%s: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). ' +
490
+ 'This component defines getSnapshotBeforeUpdate() only.',
491
+ getComponentName(ctor),
492
+ );
493
+ }
494
+
495
+ if (typeof instance.getDerivedStateFromProps === 'function') {
496
+ console.error(
497
+ '%s: getDerivedStateFromProps() is defined as an instance method ' +
498
+ 'and will be ignored. Instead, declare it as a static method.',
499
+ name,
500
+ );
501
+ }
502
+ if (typeof instance.getDerivedStateFromError === 'function') {
503
+ console.error(
504
+ '%s: getDerivedStateFromError() is defined as an instance method ' +
505
+ 'and will be ignored. Instead, declare it as a static method.',
506
+ name,
507
+ );
508
+ }
509
+ if (typeof ctor.getSnapshotBeforeUpdate === 'function') {
510
+ console.error(
511
+ '%s: getSnapshotBeforeUpdate() is defined as a static method ' +
512
+ 'and will be ignored. Instead, declare it as an instance method.',
513
+ name,
514
+ );
515
+ }
516
+ const state = instance.state;
517
+ if (state && (typeof state !== 'object' || isArray(state))) {
518
+ console.error('%s.state: must be set to an object or null', name);
519
+ }
520
+ if (
521
+ typeof instance.getChildContext === 'function' &&
522
+ typeof ctor.childContextTypes !== 'object'
523
+ ) {
524
+ console.error(
525
+ '%s.getChildContext(): childContextTypes must be defined in order to ' +
526
+ 'use getChildContext().',
527
+ name,
528
+ );
529
+ }
530
+ }
531
+}
532
+
533
+function adoptClassInstance(workInProgress: Fiber, instance: any): void {
534
+ instance.updater = classComponentUpdater;
535
+ workInProgress.stateNode = instance;
536
+ // The instance needs access to the fiber so that it can schedule updates
537
+ setInstance(instance, workInProgress);
538
+ if (__DEV__) {
539
+ instance._reactInternalInstance = fakeInternalInstance;
540
+ }
541
+}
542
+
543
+function constructClassInstance(
544
+ workInProgress: Fiber,
545
+ ctor: any,
546
+ props: any,
547
+): any {
548
+ let isLegacyContextConsumer = false;
549
+ let unmaskedContext = emptyContextObject;
550
+ let context = emptyContextObject;
551
+ const contextType = ctor.contextType;
552
+
553
+ if (__DEV__) {
554
+ if ('contextType' in ctor) {
555
+ const isValid =
556
+ // Allow null for conditional declaration
557
+ contextType === null ||
558
+ (contextType !== undefined &&
559
+ contextType.$$typeof === REACT_CONTEXT_TYPE &&
560
+ contextType._context === undefined); // Not a <Context.Consumer>
561
+
562
+ if (!isValid && !didWarnAboutInvalidateContextType.has(ctor)) {
563
+ didWarnAboutInvalidateContextType.add(ctor);
564
+
565
+ let addendum = '';
566
+ if (contextType === undefined) {
567
+ addendum =
568
+ ' However, it is set to undefined. ' +
569
+ 'This can be caused by a typo or by mixing up named and default imports. ' +
570
+ 'This can also happen due to a circular dependency, so ' +
571
+ 'try moving the createContext() call to a separate file.';
572
+ } else if (typeof contextType !== 'object') {
573
+ addendum = ' However, it is set to a ' + typeof contextType + '.';
574
+ } else if (contextType.$$typeof === REACT_PROVIDER_TYPE) {
575
+ addendum = ' Did you accidentally pass the Context.Provider instead?';
576
+ } else if (contextType._context !== undefined) {
577
+ // <Context.Consumer>
578
+ addendum = ' Did you accidentally pass the Context.Consumer instead?';
579
+ } else {
580
+ addendum =
581
+ ' However, it is set to an object with keys {' +
582
+ Object.keys(contextType).join(', ') +
583
+ '}.';
584
+ }
585
+ console.error(
586
+ '%s defines an invalid contextType. ' +
587
+ 'contextType should point to the Context object returned by React.createContext().%s',
588
+ getComponentName(ctor) || 'Component',
589
+ addendum,
590
+ );
591
+ }
592
+ }
593
+ }
594
+
595
+ if (typeof contextType === 'object' && contextType !== null) {
596
+ context = readContext((contextType: any));
597
+ } else if (!disableLegacyContext) {
598
+ unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
599
+ const contextTypes = ctor.contextTypes;
600
+ isLegacyContextConsumer =
601
+ contextTypes !== null && contextTypes !== undefined;
602
+ context = isLegacyContextConsumer
603
+ ? getMaskedContext(workInProgress, unmaskedContext)
604
+ : emptyContextObject;
605
+ }
606
+
607
+ // Instantiate twice to help detect side-effects.
608
+ if (__DEV__) {
609
+ if (
610
+ debugRenderPhaseSideEffectsForStrictMode &&
611
+ workInProgress.mode & StrictMode
612
+ ) {
613
+ disableLogs();
614
+ try {
615
+ new ctor(props, context); // eslint-disable-line no-new
616
+ } finally {
617
+ reenableLogs();
618
+ }
619
+ }
620
+ }
621
+
622
+ const instance = new ctor(props, context);
623
+ const state = (workInProgress.memoizedState =
624
+ instance.state !== null && instance.state !== undefined
625
+ ? instance.state
626
+ : null);
627
+ adoptClassInstance(workInProgress, instance);
628
+
629
+ if (__DEV__) {
630
+ if (typeof ctor.getDerivedStateFromProps === 'function' && state === null) {
631
+ const componentName = getComponentName(ctor) || 'Component';
632
+ if (!didWarnAboutUninitializedState.has(componentName)) {
633
+ didWarnAboutUninitializedState.add(componentName);
634
+ console.error(
635
+ '`%s` uses `getDerivedStateFromProps` but its initial state is ' +
636
+ '%s. This is not recommended. Instead, define the initial state by ' +
637
+ 'assigning an object to `this.state` in the constructor of `%s`. ' +
638
+ 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.',
639
+ componentName,
640
+ instance.state === null ? 'null' : 'undefined',
641
+ componentName,
642
+ );
643
+ }
644
+ }
645
+
646
+ // If new component APIs are defined, "unsafe" lifecycles won't be called.
647
+ // Warn about these lifecycles if they are present.
648
+ // Don't warn about react-lifecycles-compat polyfilled methods though.
649
+ if (
650
+ typeof ctor.getDerivedStateFromProps === 'function' ||
651
+ typeof instance.getSnapshotBeforeUpdate === 'function'
652
+ ) {
653
+ let foundWillMountName = null;
654
+ let foundWillReceivePropsName = null;
655
+ let foundWillUpdateName = null;
656
+ if (
657
+ typeof instance.componentWillMount === 'function' &&
658
+ instance.componentWillMount.__suppressDeprecationWarning !== true
659
+ ) {
660
+ foundWillMountName = 'componentWillMount';
661
+ } else if (typeof instance.UNSAFE_componentWillMount === 'function') {
662
+ foundWillMountName = 'UNSAFE_componentWillMount';
663
+ }
664
+ if (
665
+ typeof instance.componentWillReceiveProps === 'function' &&
666
+ instance.componentWillReceiveProps.__suppressDeprecationWarning !== true
667
+ ) {
668
+ foundWillReceivePropsName = 'componentWillReceiveProps';
669
+ } else if (
670
+ typeof instance.UNSAFE_componentWillReceiveProps === 'function'
671
+ ) {
672
+ foundWillReceivePropsName = 'UNSAFE_componentWillReceiveProps';
673
+ }
674
+ if (
675
+ typeof instance.componentWillUpdate === 'function' &&
676
+ instance.componentWillUpdate.__suppressDeprecationWarning !== true
677
+ ) {
678
+ foundWillUpdateName = 'componentWillUpdate';
679
+ } else if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
680
+ foundWillUpdateName = 'UNSAFE_componentWillUpdate';
681
+ }
682
+ if (
683
+ foundWillMountName !== null ||
684
+ foundWillReceivePropsName !== null ||
685
+ foundWillUpdateName !== null
686
+ ) {
687
+ const componentName = getComponentName(ctor) || 'Component';
688
+ const newApiName =
689
+ typeof ctor.getDerivedStateFromProps === 'function'
690
+ ? 'getDerivedStateFromProps()'
691
+ : 'getSnapshotBeforeUpdate()';
692
+ if (!didWarnAboutLegacyLifecyclesAndDerivedState.has(componentName)) {
693
+ didWarnAboutLegacyLifecyclesAndDerivedState.add(componentName);
694
+ console.error(
695
+ 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
696
+ '%s uses %s but also contains the following legacy lifecycles:%s%s%s\n\n' +
697
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
698
+ 'https://fb.me/react-unsafe-component-lifecycles',
699
+ componentName,
700
+ newApiName,
701
+ foundWillMountName !== null ? `\n ${foundWillMountName}` : '',
702
+ foundWillReceivePropsName !== null
703
+ ? `\n ${foundWillReceivePropsName}`
704
+ : '',
705
+ foundWillUpdateName !== null ? `\n ${foundWillUpdateName}` : '',
706
+ );
707
+ }
708
+ }
709
+ }
710
+ }
711
+
712
+ // Cache unmasked context so we can avoid recreating masked context unless necessary.
713
+ // ReactFiberContext usually updates this cache but can't for newly-created instances.
714
+ if (isLegacyContextConsumer) {
715
+ cacheContext(workInProgress, unmaskedContext, context);
716
+ }
717
+
718
+ return instance;
719
+}
720
+
721
+function callComponentWillMount(workInProgress, instance) {
722
+ const oldState = instance.state;
723
+
724
+ if (typeof instance.componentWillMount === 'function') {
725
+ instance.componentWillMount();
726
+ }
727
+ if (typeof instance.UNSAFE_componentWillMount === 'function') {
728
+ instance.UNSAFE_componentWillMount();
729
+ }
730
+
731
+ if (oldState !== instance.state) {
732
+ if (__DEV__) {
733
+ console.error(
734
+ '%s.componentWillMount(): Assigning directly to this.state is ' +
735
+ "deprecated (except inside a component's " +
736
+ 'constructor). Use setState instead.',
737
+ getComponentName(workInProgress.type) || 'Component',
738
+ );
739
+ }
740
+ classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
741
+ }
742
+}
743
+
744
+function callComponentWillReceiveProps(
745
+ workInProgress,
746
+ instance,
747
+ newProps,
748
+ nextContext,
749
+) {
750
+ const oldState = instance.state;
751
+ if (typeof instance.componentWillReceiveProps === 'function') {
752
+ instance.componentWillReceiveProps(newProps, nextContext);
753
+ }
754
+ if (typeof instance.UNSAFE_componentWillReceiveProps === 'function') {
755
+ instance.UNSAFE_componentWillReceiveProps(newProps, nextContext);
756
+ }
757
+
758
+ if (instance.state !== oldState) {
759
+ if (__DEV__) {
760
+ const componentName =
761
+ getComponentName(workInProgress.type) || 'Component';
762
+ if (!didWarnAboutStateAssignmentForComponent.has(componentName)) {
763
+ didWarnAboutStateAssignmentForComponent.add(componentName);
764
+ console.error(
765
+ '%s.componentWillReceiveProps(): Assigning directly to ' +
766
+ "this.state is deprecated (except inside a component's " +
767
+ 'constructor). Use setState instead.',
768
+ componentName,
769
+ );
770
+ }
771
+ }
772
+ classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
773
+ }
774
+}
775
+
776
+// Invokes the mount life-cycles on a previously never rendered instance.
777
+function mountClassInstance(
778
+ workInProgress: Fiber,
779
+ ctor: any,
780
+ newProps: any,
781
+ renderExpirationTime: ExpirationTime,
782
+): void {
783
+ if (__DEV__) {
784
+ checkClassInstance(workInProgress, ctor, newProps);
785
+ }
786
+
787
+ const instance = workInProgress.stateNode;
788
+ instance.props = newProps;
789
+ instance.state = workInProgress.memoizedState;
790
+ instance.refs = emptyRefsObject;
791
+
792
+ initializeUpdateQueue(workInProgress);
793
+
794
+ const contextType = ctor.contextType;
795
+ if (typeof contextType === 'object' && contextType !== null) {
796
+ instance.context = readContext(contextType);
797
+ } else if (disableLegacyContext) {
798
+ instance.context = emptyContextObject;
799
+ } else {
800
+ const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
801
+ instance.context = getMaskedContext(workInProgress, unmaskedContext);
802
+ }
803
+
804
+ if (__DEV__) {
805
+ if (instance.state === newProps) {
806
+ const componentName = getComponentName(ctor) || 'Component';
807
+ if (!didWarnAboutDirectlyAssigningPropsToState.has(componentName)) {
808
+ didWarnAboutDirectlyAssigningPropsToState.add(componentName);
809
+ console.error(
810
+ '%s: It is not recommended to assign props directly to state ' +
811
+ "because updates to props won't be reflected in state. " +
812
+ 'In most cases, it is better to use props directly.',
813
+ componentName,
814
+ );
815
+ }
816
+ }
817
+
818
+ if (workInProgress.mode & StrictMode) {
819
+ ReactStrictModeWarnings.recordLegacyContextWarning(
820
+ workInProgress,
821
+ instance,
822
+ );
823
+ }
824
+
825
+ if (warnAboutDeprecatedLifecycles) {
826
+ ReactStrictModeWarnings.recordUnsafeLifecycleWarnings(
827
+ workInProgress,
828
+ instance,
829
+ );
830
+ }
831
+ }
832
+
833
+ processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
834
+ instance.state = workInProgress.memoizedState;
835
+
836
+ const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
837
+ if (typeof getDerivedStateFromProps === 'function') {
838
+ applyDerivedStateFromProps(
839
+ workInProgress,
840
+ ctor,
841
+ getDerivedStateFromProps,
842
+ newProps,
843
+ );
844
+ instance.state = workInProgress.memoizedState;
845
+ }
846
+
847
+ // In order to support react-lifecycles-compat polyfilled components,
848
+ // Unsafe lifecycles should not be invoked for components using the new APIs.
849
+ if (
850
+ typeof ctor.getDerivedStateFromProps !== 'function' &&
851
+ typeof instance.getSnapshotBeforeUpdate !== 'function' &&
852
+ (typeof instance.UNSAFE_componentWillMount === 'function' ||
853
+ typeof instance.componentWillMount === 'function')
854
+ ) {
855
+ callComponentWillMount(workInProgress, instance);
856
+ // If we had additional state updates during this life-cycle, let's
857
+ // process them now.
858
+ processUpdateQueue(
859
+ workInProgress,
860
+ newProps,
861
+ instance,
862
+ renderExpirationTime,
863
+ );
864
+ instance.state = workInProgress.memoizedState;
865
+ }
866
+
867
+ if (typeof instance.componentDidMount === 'function') {
868
+ workInProgress.effectTag |= Update;
869
+ }
870
+}
871
+
872
+function resumeMountClassInstance(
873
+ workInProgress: Fiber,
874
+ ctor: any,
875
+ newProps: any,
876
+ renderExpirationTime: ExpirationTime,
877
+): boolean {
878
+ const instance = workInProgress.stateNode;
879
+
880
+ const oldProps = workInProgress.memoizedProps;
881
+ instance.props = oldProps;
882
+
883
+ const oldContext = instance.context;
884
+ const contextType = ctor.contextType;
885
+ let nextContext = emptyContextObject;
886
+ if (typeof contextType === 'object' && contextType !== null) {
887
+ nextContext = readContext(contextType);
888
+ } else if (!disableLegacyContext) {
889
+ const nextLegacyUnmaskedContext = getUnmaskedContext(
890
+ workInProgress,
891
+ ctor,
892
+ true,
893
+ );
894
+ nextContext = getMaskedContext(workInProgress, nextLegacyUnmaskedContext);
895
+ }
896
+
897
+ const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
898
+ const hasNewLifecycles =
899
+ typeof getDerivedStateFromProps === 'function' ||
900
+ typeof instance.getSnapshotBeforeUpdate === 'function';
901
+
902
+ // Note: During these life-cycles, instance.props/instance.state are what
903
+ // ever the previously attempted to render - not the "current". However,
904
+ // during componentDidUpdate we pass the "current" props.
905
+
906
+ // In order to support react-lifecycles-compat polyfilled components,
907
+ // Unsafe lifecycles should not be invoked for components using the new APIs.
908
+ if (
909
+ !hasNewLifecycles &&
910
+ (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
911
+ typeof instance.componentWillReceiveProps === 'function')
912
+ ) {
913
+ if (oldProps !== newProps || oldContext !== nextContext) {
914
+ callComponentWillReceiveProps(
915
+ workInProgress,
916
+ instance,
917
+ newProps,
918
+ nextContext,
919
+ );
920
+ }
921
+ }
922
+
923
+ resetHasForceUpdateBeforeProcessing();
924
+
925
+ const oldState = workInProgress.memoizedState;
926
+ let newState = (instance.state = oldState);
927
+ processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
928
+ newState = workInProgress.memoizedState;
929
+ if (
930
+ oldProps === newProps &&
931
+ oldState === newState &&
932
+ !hasContextChanged() &&
933
+ !checkHasForceUpdateAfterProcessing()
934
+ ) {
935
+ // If an update was already in progress, we should schedule an Update
936
+ // effect even though we're bailing out, so that cWU/cDU are called.
937
+ if (typeof instance.componentDidMount === 'function') {
938
+ workInProgress.effectTag |= Update;
939
+ }
940
+ return false;
941
+ }
942
+
943
+ if (typeof getDerivedStateFromProps === 'function') {
944
+ applyDerivedStateFromProps(
945
+ workInProgress,
946
+ ctor,
947
+ getDerivedStateFromProps,
948
+ newProps,
949
+ );
950
+ newState = workInProgress.memoizedState;
951
+ }
952
+
953
+ const shouldUpdate =
954
+ checkHasForceUpdateAfterProcessing() ||
955
+ checkShouldComponentUpdate(
956
+ workInProgress,
957
+ ctor,
958
+ oldProps,
959
+ newProps,
960
+ oldState,
961
+ newState,
962
+ nextContext,
963
+ );
964
+
965
+ if (shouldUpdate) {
966
+ // In order to support react-lifecycles-compat polyfilled components,
967
+ // Unsafe lifecycles should not be invoked for components using the new APIs.
968
+ if (
969
+ !hasNewLifecycles &&
970
+ (typeof instance.UNSAFE_componentWillMount === 'function' ||
971
+ typeof instance.componentWillMount === 'function')
972
+ ) {
973
+ if (typeof instance.componentWillMount === 'function') {
974
+ instance.componentWillMount();
975
+ }
976
+ if (typeof instance.UNSAFE_componentWillMount === 'function') {
977
+ instance.UNSAFE_componentWillMount();
978
+ }
979
+ }
980
+ if (typeof instance.componentDidMount === 'function') {
981
+ workInProgress.effectTag |= Update;
982
+ }
983
+ } else {
984
+ // If an update was already in progress, we should schedule an Update
985
+ // effect even though we're bailing out, so that cWU/cDU are called.
986
+ if (typeof instance.componentDidMount === 'function') {
987
+ workInProgress.effectTag |= Update;
988
+ }
989
+
990
+ // If shouldComponentUpdate returned false, we should still update the
991
+ // memoized state to indicate that this work can be reused.
992
+ workInProgress.memoizedProps = newProps;
993
+ workInProgress.memoizedState = newState;
994
+ }
995
+
996
+ // Update the existing instance's state, props, and context pointers even
997
+ // if shouldComponentUpdate returns false.
998
+ instance.props = newProps;
999
+ instance.state = newState;
1000
+ instance.context = nextContext;
1001
+
1002
+ return shouldUpdate;
1003
+}
1004
+
1005
+// Invokes the update life-cycles and returns false if it shouldn't rerender.
1006
+function updateClassInstance(
1007
+ current: Fiber,
1008
+ workInProgress: Fiber,
1009
+ ctor: any,
1010
+ newProps: any,
1011
+ renderExpirationTime: ExpirationTime,
1012
+): boolean {
1013
+ const instance = workInProgress.stateNode;
1014
+
1015
+ cloneUpdateQueue(current, workInProgress);
1016
+
1017
+ const unresolvedOldProps = workInProgress.memoizedProps;
1018
+ const oldProps =
1019
+ workInProgress.type === workInProgress.elementType
1020
+ ? unresolvedOldProps
1021
+ : resolveDefaultProps(workInProgress.type, unresolvedOldProps);
1022
+ instance.props = oldProps;
1023
+ const unresolvedNewProps = workInProgress.pendingProps;
1024
+
1025
+ const oldContext = instance.context;
1026
+ const contextType = ctor.contextType;
1027
+ let nextContext = emptyContextObject;
1028
+ if (typeof contextType === 'object' && contextType !== null) {
1029
+ nextContext = readContext(contextType);
1030
+ } else if (!disableLegacyContext) {
1031
+ const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
1032
+ nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
1033
+ }
1034
+
1035
+ const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
1036
+ const hasNewLifecycles =
1037
+ typeof getDerivedStateFromProps === 'function' ||
1038
+ typeof instance.getSnapshotBeforeUpdate === 'function';
1039
+
1040
+ // Note: During these life-cycles, instance.props/instance.state are what
1041
+ // ever the previously attempted to render - not the "current". However,
1042
+ // during componentDidUpdate we pass the "current" props.
1043
+
1044
+ // In order to support react-lifecycles-compat polyfilled components,
1045
+ // Unsafe lifecycles should not be invoked for components using the new APIs.
1046
+ if (
1047
+ !hasNewLifecycles &&
1048
+ (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
1049
+ typeof instance.componentWillReceiveProps === 'function')
1050
+ ) {
1051
+ if (
1052
+ unresolvedOldProps !== unresolvedNewProps ||
1053
+ oldContext !== nextContext
1054
+ ) {
1055
+ callComponentWillReceiveProps(
1056
+ workInProgress,
1057
+ instance,
1058
+ newProps,
1059
+ nextContext,
1060
+ );
1061
+ }
1062
+ }
1063
+
1064
+ resetHasForceUpdateBeforeProcessing();
1065
+
1066
+ const oldState = workInProgress.memoizedState;
1067
+ let newState = (instance.state = oldState);
1068
+ processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
1069
+ newState = workInProgress.memoizedState;
1070
+
1071
+ if (
1072
+ unresolvedOldProps === unresolvedNewProps &&
1073
+ oldState === newState &&
1074
+ !hasContextChanged() &&
1075
+ !checkHasForceUpdateAfterProcessing()
1076
+ ) {
1077
+ // If an update was already in progress, we should schedule an Update
1078
+ // effect even though we're bailing out, so that cWU/cDU are called.
1079
+ if (typeof instance.componentDidUpdate === 'function') {
1080
+ if (
1081
+ unresolvedOldProps !== current.memoizedProps ||
1082
+ oldState !== current.memoizedState
1083
+ ) {
1084
+ workInProgress.effectTag |= Update;
1085
+ }
1086
+ }
1087
+ if (typeof instance.getSnapshotBeforeUpdate === 'function') {
1088
+ if (
1089
+ unresolvedOldProps !== current.memoizedProps ||
1090
+ oldState !== current.memoizedState
1091
+ ) {
1092
+ workInProgress.effectTag |= Snapshot;
1093
+ }
1094
+ }
1095
+ return false;
1096
+ }
1097
+
1098
+ if (typeof getDerivedStateFromProps === 'function') {
1099
+ applyDerivedStateFromProps(
1100
+ workInProgress,
1101
+ ctor,
1102
+ getDerivedStateFromProps,
1103
+ newProps,
1104
+ );
1105
+ newState = workInProgress.memoizedState;
1106
+ }
1107
+
1108
+ const shouldUpdate =
1109
+ checkHasForceUpdateAfterProcessing() ||
1110
+ checkShouldComponentUpdate(
1111
+ workInProgress,
1112
+ ctor,
1113
+ oldProps,
1114
+ newProps,
1115
+ oldState,
1116
+ newState,
1117
+ nextContext,
1118
+ );
1119
+
1120
+ if (shouldUpdate) {
1121
+ // In order to support react-lifecycles-compat polyfilled components,
1122
+ // Unsafe lifecycles should not be invoked for components using the new APIs.
1123
+ if (
1124
+ !hasNewLifecycles &&
1125
+ (typeof instance.UNSAFE_componentWillUpdate === 'function' ||
1126
+ typeof instance.componentWillUpdate === 'function')
1127
+ ) {
1128
+ if (typeof instance.componentWillUpdate === 'function') {
1129
+ instance.componentWillUpdate(newProps, newState, nextContext);
1130
+ }
1131
+ if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
1132
+ instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
1133
+ }
1134
+ }
1135
+ if (typeof instance.componentDidUpdate === 'function') {
1136
+ workInProgress.effectTag |= Update;
1137
+ }
1138
+ if (typeof instance.getSnapshotBeforeUpdate === 'function') {
1139
+ workInProgress.effectTag |= Snapshot;
1140
+ }
1141
+ } else {
1142
+ // If an update was already in progress, we should schedule an Update
1143
+ // effect even though we're bailing out, so that cWU/cDU are called.
1144
+ if (typeof instance.componentDidUpdate === 'function') {
1145
+ if (
1146
+ unresolvedOldProps !== current.memoizedProps ||
1147
+ oldState !== current.memoizedState
1148
+ ) {
1149
+ workInProgress.effectTag |= Update;
1150
+ }
1151
+ }
1152
+ if (typeof instance.getSnapshotBeforeUpdate === 'function') {
1153
+ if (
1154
+ unresolvedOldProps !== current.memoizedProps ||
1155
+ oldState !== current.memoizedState
1156
+ ) {
1157
+ workInProgress.effectTag |= Snapshot;
1158
+ }
1159
+ }
1160
+
1161
+ // If shouldComponentUpdate returned false, we should still update the
1162
+ // memoized props/state to indicate that this work can be reused.
1163
+ workInProgress.memoizedProps = newProps;
1164
+ workInProgress.memoizedState = newState;
1165
+ }
1166
+
1167
+ // Update the existing instance's state, props, and context pointers even
1168
+ // if shouldComponentUpdate returns false.
1169
+ instance.props = newProps;
1170
+ instance.state = newState;
1171
+ instance.context = nextContext;
1172
+
1173
+ return shouldUpdate;
1174
+}
1175
+
1176
+export {
1177
+ adoptClassInstance,
1178
+ constructClassInstance,
1179
+ mountClassInstance,
1180
+ resumeMountClassInstance,
1181
+ updateClassInstance,
1182
+};
packages/react-reconciler/src/ReactFiberCommitWork.new.js
new
+1837
@@ -0,0 +1,1837 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {
11
+ Instance,
12
+ TextInstance,
13
+ SuspenseInstance,
14
+ Container,
15
+ ChildSet,
16
+ UpdatePayload,
17
+} from './ReactFiberHostConfig';
18
+import type {Fiber} from './ReactInternalTypes';
19
+import type {FiberRoot} from './ReactInternalTypes';
20
+import type {ExpirationTime} from './ReactFiberExpirationTime';
21
+import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
22
+import type {UpdateQueue} from './ReactUpdateQueue.new';
23
+import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
24
+import type {Wakeable} from 'shared/ReactTypes';
25
+import type {ReactPriorityLevel} from './ReactInternalTypes';
26
+
27
+import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
28
+import {
29
+ deferPassiveEffectCleanupDuringUnmount,
30
+ enableSchedulerTracing,
31
+ enableProfilerTimer,
32
+ enableProfilerCommitHooks,
33
+ enableSuspenseServerRenderer,
34
+ enableDeprecatedFlareAPI,
35
+ enableFundamentalAPI,
36
+ enableSuspenseCallback,
37
+ enableScopeAPI,
38
+ runAllPassiveEffectDestroysBeforeCreates,
39
+ enableUseEventAPI,
40
+} from 'shared/ReactFeatureFlags';
41
+import {
42
+ FunctionComponent,
43
+ ForwardRef,
44
+ ClassComponent,
45
+ HostRoot,
46
+ HostComponent,
47
+ HostText,
48
+ HostPortal,
49
+ Profiler,
50
+ SuspenseComponent,
51
+ DehydratedFragment,
52
+ IncompleteClassComponent,
53
+ MemoComponent,
54
+ SimpleMemoComponent,
55
+ SuspenseListComponent,
56
+ FundamentalComponent,
57
+ ScopeComponent,
58
+ Block,
59
+} from './ReactWorkTags';
60
+import {
61
+ invokeGuardedCallback,
62
+ hasCaughtError,
63
+ clearCaughtError,
64
+} from 'shared/ReactErrorUtils';
65
+import {
66
+ NoEffect,
67
+ ContentReset,
68
+ Placement,
69
+ Snapshot,
70
+ Update,
71
+ Passive,
72
+} from './ReactSideEffectTags';
73
+import getComponentName from 'shared/getComponentName';
74
+import invariant from 'shared/invariant';
75
+
76
+import {onCommitUnmount} from './ReactFiberDevToolsHook.new';
77
+import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
78
+import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
79
+import {
80
+ getCommitTime,
81
+ recordLayoutEffectDuration,
82
+ recordPassiveEffectDuration,
83
+ startLayoutEffectTimer,
84
+ startPassiveEffectTimer,
85
+} from './ReactProfilerTimer.new';
86
+import {ProfileMode} from './ReactTypeOfMode';
87
+import {commitUpdateQueue} from './ReactUpdateQueue.new';
88
+import {
89
+ getPublicInstance,
90
+ supportsMutation,
91
+ supportsPersistence,
92
+ supportsHydration,
93
+ commitMount,
94
+ commitUpdate,
95
+ resetTextContent,
96
+ commitTextUpdate,
97
+ appendChild,
98
+ appendChildToContainer,
99
+ insertBefore,
100
+ insertInContainerBefore,
101
+ removeChild,
102
+ removeChildFromContainer,
103
+ clearSuspenseBoundary,
104
+ clearSuspenseBoundaryFromContainer,
105
+ replaceContainerChildren,
106
+ createContainerChildSet,
107
+ hideInstance,
108
+ hideTextInstance,
109
+ unhideInstance,
110
+ unhideTextInstance,
111
+ unmountFundamentalComponent,
112
+ updateFundamentalComponent,
113
+ commitHydratedContainer,
114
+ commitHydratedSuspenseInstance,
115
+ beforeRemoveInstance,
116
+} from './ReactFiberHostConfig';
117
+import {
118
+ captureCommitPhaseError,
119
+ resolveRetryWakeable,
120
+ markCommitTimeOfFallback,
121
+ enqueuePendingPassiveHookEffectMount,
122
+ enqueuePendingPassiveHookEffectUnmount,
123
+ enqueuePendingPassiveProfilerEffect,
124
+} from './ReactFiberWorkLoop.new';
125
+import {
126
+ NoEffect as NoHookEffect,
127
+ HasEffect as HookHasEffect,
128
+ Layout as HookLayout,
129
+ Passive as HookPassive,
130
+} from './ReactHookEffectTags';
131
+import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.new';
132
+import {
133
+ runWithPriority,
134
+ NormalPriority,
135
+} from './SchedulerWithReactIntegration.new';
136
+import {
137
+ updateDeprecatedEventListeners,
138
+ unmountDeprecatedResponderListeners,
139
+} from './ReactFiberDeprecatedEvents.new';
140
+
141
+let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
142
+if (__DEV__) {
143
+ didWarnAboutUndefinedSnapshotBeforeUpdate = new Set();
144
+}
145
+
146
+const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
147
+
148
+const callComponentWillUnmountWithTimer = function(current, instance) {
149
+ instance.props = current.memoizedProps;
150
+ instance.state = current.memoizedState;
151
+ if (
152
+ enableProfilerTimer &&
153
+ enableProfilerCommitHooks &&
154
+ current.mode & ProfileMode
155
+ ) {
156
+ try {
157
+ startLayoutEffectTimer();
158
+ instance.componentWillUnmount();
159
+ } finally {
160
+ recordLayoutEffectDuration(current);
161
+ }
162
+ } else {
163
+ instance.componentWillUnmount();
164
+ }
165
+};
166
+
167
+// Capture errors so they don't interrupt unmounting.
168
+function safelyCallComponentWillUnmount(current, instance) {
169
+ if (__DEV__) {
170
+ invokeGuardedCallback(
171
+ null,
172
+ callComponentWillUnmountWithTimer,
173
+ null,
174
+ current,
175
+ instance,
176
+ );
177
+ if (hasCaughtError()) {
178
+ const unmountError = clearCaughtError();
179
+ captureCommitPhaseError(current, unmountError);
180
+ }
181
+ } else {
182
+ try {
183
+ callComponentWillUnmountWithTimer(current, instance);
184
+ } catch (unmountError) {
185
+ captureCommitPhaseError(current, unmountError);
186
+ }
187
+ }
188
+}
189
+
190
+function safelyDetachRef(current: Fiber) {
191
+ const ref = current.ref;
192
+ if (ref !== null) {
193
+ if (typeof ref === 'function') {
194
+ if (__DEV__) {
195
+ invokeGuardedCallback(null, ref, null, null);
196
+ if (hasCaughtError()) {
197
+ const refError = clearCaughtError();
198
+ captureCommitPhaseError(current, refError);
199
+ }
200
+ } else {
201
+ try {
202
+ ref(null);
203
+ } catch (refError) {
204
+ captureCommitPhaseError(current, refError);
205
+ }
206
+ }
207
+ } else {
208
+ ref.current = null;
209
+ }
210
+ }
211
+}
212
+
213
+function safelyCallDestroy(current, destroy) {
214
+ if (__DEV__) {
215
+ invokeGuardedCallback(null, destroy, null);
216
+ if (hasCaughtError()) {
217
+ const error = clearCaughtError();
218
+ captureCommitPhaseError(current, error);
219
+ }
220
+ } else {
221
+ try {
222
+ destroy();
223
+ } catch (error) {
224
+ captureCommitPhaseError(current, error);
225
+ }
226
+ }
227
+}
228
+
229
+function commitBeforeMutationLifeCycles(
230
+ current: Fiber | null,
231
+ finishedWork: Fiber,
232
+): void {
233
+ switch (finishedWork.tag) {
234
+ case FunctionComponent:
235
+ case ForwardRef:
236
+ case SimpleMemoComponent:
237
+ case Block: {
238
+ return;
239
+ }
240
+ case ClassComponent: {
241
+ if (finishedWork.effectTag & Snapshot) {
242
+ if (current !== null) {
243
+ const prevProps = current.memoizedProps;
244
+ const prevState = current.memoizedState;
245
+ const instance = finishedWork.stateNode;
246
+ // We could update instance props and state here,
247
+ // but instead we rely on them being set during last render.
248
+ // TODO: revisit this when we implement resuming.
249
+ if (__DEV__) {
250
+ if (
251
+ finishedWork.type === finishedWork.elementType &&
252
+ !didWarnAboutReassigningProps
253
+ ) {
254
+ if (instance.props !== finishedWork.memoizedProps) {
255
+ console.error(
256
+ 'Expected %s props to match memoized props before ' +
257
+ 'getSnapshotBeforeUpdate. ' +
258
+ 'This might either be because of a bug in React, or because ' +
259
+ 'a component reassigns its own `this.props`. ' +
260
+ 'Please file an issue.',
261
+ getComponentName(finishedWork.type) || 'instance',
262
+ );
263
+ }
264
+ if (instance.state !== finishedWork.memoizedState) {
265
+ console.error(
266
+ 'Expected %s state to match memoized state before ' +
267
+ 'getSnapshotBeforeUpdate. ' +
268
+ 'This might either be because of a bug in React, or because ' +
269
+ 'a component reassigns its own `this.state`. ' +
270
+ 'Please file an issue.',
271
+ getComponentName(finishedWork.type) || 'instance',
272
+ );
273
+ }
274
+ }
275
+ }
276
+ const snapshot = instance.getSnapshotBeforeUpdate(
277
+ finishedWork.elementType === finishedWork.type
278
+ ? prevProps
279
+ : resolveDefaultProps(finishedWork.type, prevProps),
280
+ prevState,
281
+ );
282
+ if (__DEV__) {
283
+ const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<mixed>);
284
+ if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
285
+ didWarnSet.add(finishedWork.type);
286
+ console.error(
287
+ '%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
288
+ 'must be returned. You have returned undefined.',
289
+ getComponentName(finishedWork.type),
290
+ );
291
+ }
292
+ }
293
+ instance.__reactInternalSnapshotBeforeUpdate = snapshot;
294
+ }
295
+ }
296
+ return;
297
+ }
298
+ case HostRoot:
299
+ case HostComponent:
300
+ case HostText:
301
+ case HostPortal:
302
+ case IncompleteClassComponent:
303
+ // Nothing to do for these component types
304
+ return;
305
+ }
306
+ invariant(
307
+ false,
308
+ 'This unit of work tag should not have side-effects. This error is ' +
309
+ 'likely caused by a bug in React. Please file an issue.',
310
+ );
311
+}
312
+
313
+function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) {
314
+ const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
315
+ const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
316
+ if (lastEffect !== null) {
317
+ const firstEffect = lastEffect.next;
318
+ let effect = firstEffect;
319
+ do {
320
+ if ((effect.tag & tag) === tag) {
321
+ // Unmount
322
+ const destroy = effect.destroy;
323
+ effect.destroy = undefined;
324
+ if (destroy !== undefined) {
325
+ destroy();
326
+ }
327
+ }
328
+ effect = effect.next;
329
+ } while (effect !== firstEffect);
330
+ }
331
+}
332
+
333
+function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
334
+ const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
335
+ const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
336
+ if (lastEffect !== null) {
337
+ const firstEffect = lastEffect.next;
338
+ let effect = firstEffect;
339
+ do {
340
+ if ((effect.tag & tag) === tag) {
341
+ // Mount
342
+ const create = effect.create;
343
+ effect.destroy = create();
344
+
345
+ if (__DEV__) {
346
+ const destroy = effect.destroy;
347
+ if (destroy !== undefined && typeof destroy !== 'function') {
348
+ let addendum;
349
+ if (destroy === null) {
350
+ addendum =
351
+ ' You returned null. If your effect does not require clean ' +
352
+ 'up, return undefined (or nothing).';
353
+ } else if (typeof destroy.then === 'function') {
354
+ addendum =
355
+ '\n\nIt looks like you wrote useEffect(async () => ...) or returned a Promise. ' +
356
+ 'Instead, write the async function inside your effect ' +
357
+ 'and call it immediately:\n\n' +
358
+ 'useEffect(() => {\n' +
359
+ ' async function fetchData() {\n' +
360
+ ' // You can await here\n' +
361
+ ' const response = await MyAPI.getData(someId);\n' +
362
+ ' // ...\n' +
363
+ ' }\n' +
364
+ ' fetchData();\n' +
365
+ `}, [someId]); // Or [] if effect doesn't need props or state\n\n` +
366
+ 'Learn more about data fetching with Hooks: https://fb.me/react-hooks-data-fetching';
367
+ } else {
368
+ addendum = ' You returned: ' + destroy;
369
+ }
370
+ console.error(
371
+ 'An effect function must not return anything besides a function, ' +
372
+ 'which is used for clean-up.%s%s',
373
+ addendum,
374
+ getStackByFiberInDevAndProd(finishedWork),
375
+ );
376
+ }
377
+ }
378
+ }
379
+ effect = effect.next;
380
+ } while (effect !== firstEffect);
381
+ }
382
+}
383
+
384
+function schedulePassiveEffects(finishedWork: Fiber) {
385
+ if (runAllPassiveEffectDestroysBeforeCreates) {
386
+ const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
387
+ const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
388
+ if (lastEffect !== null) {
389
+ const firstEffect = lastEffect.next;
390
+ let effect = firstEffect;
391
+ do {
392
+ const {next, tag} = effect;
393
+ if (
394
+ (tag & HookPassive) !== NoHookEffect &&
395
+ (tag & HookHasEffect) !== NoHookEffect
396
+ ) {
397
+ enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
398
+ enqueuePendingPassiveHookEffectMount(finishedWork, effect);
399
+ }
400
+ effect = next;
401
+ } while (effect !== firstEffect);
402
+ }
403
+ }
404
+}
405
+
406
+export function commitPassiveHookEffects(finishedWork: Fiber): void {
407
+ if ((finishedWork.effectTag & Passive) !== NoEffect) {
408
+ switch (finishedWork.tag) {
409
+ case FunctionComponent:
410
+ case ForwardRef:
411
+ case SimpleMemoComponent:
412
+ case Block: {
413
+ // TODO (#17945) We should call all passive destroy functions (for all fibers)
414
+ // before calling any create functions. The current approach only serializes
415
+ // these for a single fiber.
416
+ if (
417
+ enableProfilerTimer &&
418
+ enableProfilerCommitHooks &&
419
+ finishedWork.mode & ProfileMode
420
+ ) {
421
+ try {
422
+ startPassiveEffectTimer();
423
+ commitHookEffectListUnmount(
424
+ HookPassive | HookHasEffect,
425
+ finishedWork,
426
+ );
427
+ commitHookEffectListMount(
428
+ HookPassive | HookHasEffect,
429
+ finishedWork,
430
+ );
431
+ } finally {
432
+ recordPassiveEffectDuration(finishedWork);
433
+ }
434
+ } else {
435
+ commitHookEffectListUnmount(
436
+ HookPassive | HookHasEffect,
437
+ finishedWork,
438
+ );
439
+ commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
440
+ }
441
+ break;
442
+ }
443
+ default:
444
+ break;
445
+ }
446
+ }
447
+}
448
+
449
+export function commitPassiveEffectDurations(
450
+ finishedRoot: FiberRoot,
451
+ finishedWork: Fiber,
452
+): void {
453
+ if (enableProfilerTimer && enableProfilerCommitHooks) {
454
+ // Only Profilers with work in their subtree will have an Update effect scheduled.
455
+ if ((finishedWork.effectTag & Update) !== NoEffect) {
456
+ switch (finishedWork.tag) {
457
+ case Profiler: {
458
+ const {passiveEffectDuration} = finishedWork.stateNode;
459
+ const {id, onPostCommit} = finishedWork.memoizedProps;
460
+
461
+ // This value will still reflect the previous commit phase.
462
+ // It does not get reset until the start of the next commit phase.
463
+ const commitTime = getCommitTime();
464
+
465
+ if (typeof onPostCommit === 'function') {
466
+ if (enableSchedulerTracing) {
467
+ onPostCommit(
468
+ id,
469
+ finishedWork.alternate === null ? 'mount' : 'update',
470
+ passiveEffectDuration,
471
+ commitTime,
472
+ finishedRoot.memoizedInteractions,
473
+ );
474
+ } else {
475
+ onPostCommit(
476
+ id,
477
+ finishedWork.alternate === null ? 'mount' : 'update',
478
+ passiveEffectDuration,
479
+ commitTime,
480
+ );
481
+ }
482
+ }
483
+
484
+ // Bubble times to the next nearest ancestor Profiler.
485
+ // After we process that Profiler, we'll bubble further up.
486
+ let parentFiber = finishedWork.return;
487
+ while (parentFiber !== null) {
488
+ if (parentFiber.tag === Profiler) {
489
+ const parentStateNode = parentFiber.stateNode;
490
+ parentStateNode.passiveEffectDuration += passiveEffectDuration;
491
+ break;
492
+ }
493
+ parentFiber = parentFiber.return;
494
+ }
495
+ break;
496
+ }
497
+ default:
498
+ break;
499
+ }
500
+ }
501
+ }
502
+}
503
+
504
+function commitLifeCycles(
505
+ finishedRoot: FiberRoot,
506
+ current: Fiber | null,
507
+ finishedWork: Fiber,
508
+ committedExpirationTime: ExpirationTime,
509
+): void {
510
+ switch (finishedWork.tag) {
511
+ case FunctionComponent:
512
+ case ForwardRef:
513
+ case SimpleMemoComponent:
514
+ case Block: {
515
+ // At this point layout effects have already been destroyed (during mutation phase).
516
+ // This is done to prevent sibling component effects from interfering with each other,
517
+ // e.g. a destroy function in one component should never override a ref set
518
+ // by a create function in another component during the same commit.
519
+ if (
520
+ enableProfilerTimer &&
521
+ enableProfilerCommitHooks &&
522
+ finishedWork.mode & ProfileMode
523
+ ) {
524
+ try {
525
+ startLayoutEffectTimer();
526
+ commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
527
+ } finally {
528
+ recordLayoutEffectDuration(finishedWork);
529
+ }
530
+ } else {
531
+ commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
532
+ }
533
+
534
+ if (runAllPassiveEffectDestroysBeforeCreates) {
535
+ schedulePassiveEffects(finishedWork);
536
+ }
537
+ return;
538
+ }
539
+ case ClassComponent: {
540
+ const instance = finishedWork.stateNode;
541
+ if (finishedWork.effectTag & Update) {
542
+ if (current === null) {
543
+ // We could update instance props and state here,
544
+ // but instead we rely on them being set during last render.
545
+ // TODO: revisit this when we implement resuming.
546
+ if (__DEV__) {
547
+ if (
548
+ finishedWork.type === finishedWork.elementType &&
549
+ !didWarnAboutReassigningProps
550
+ ) {
551
+ if (instance.props !== finishedWork.memoizedProps) {
552
+ console.error(
553
+ 'Expected %s props to match memoized props before ' +
554
+ 'componentDidMount. ' +
555
+ 'This might either be because of a bug in React, or because ' +
556
+ 'a component reassigns its own `this.props`. ' +
557
+ 'Please file an issue.',
558
+ getComponentName(finishedWork.type) || 'instance',
559
+ );
560
+ }
561
+ if (instance.state !== finishedWork.memoizedState) {
562
+ console.error(
563
+ 'Expected %s state to match memoized state before ' +
564
+ 'componentDidMount. ' +
565
+ 'This might either be because of a bug in React, or because ' +
566
+ 'a component reassigns its own `this.state`. ' +
567
+ 'Please file an issue.',
568
+ getComponentName(finishedWork.type) || 'instance',
569
+ );
570
+ }
571
+ }
572
+ }
573
+ if (
574
+ enableProfilerTimer &&
575
+ enableProfilerCommitHooks &&
576
+ finishedWork.mode & ProfileMode
577
+ ) {
578
+ try {
579
+ startLayoutEffectTimer();
580
+ instance.componentDidMount();
581
+ } finally {
582
+ recordLayoutEffectDuration(finishedWork);
583
+ }
584
+ } else {
585
+ instance.componentDidMount();
586
+ }
587
+ } else {
588
+ const prevProps =
589
+ finishedWork.elementType === finishedWork.type
590
+ ? current.memoizedProps
591
+ : resolveDefaultProps(finishedWork.type, current.memoizedProps);
592
+ const prevState = current.memoizedState;
593
+ // We could update instance props and state here,
594
+ // but instead we rely on them being set during last render.
595
+ // TODO: revisit this when we implement resuming.
596
+ if (__DEV__) {
597
+ if (
598
+ finishedWork.type === finishedWork.elementType &&
599
+ !didWarnAboutReassigningProps
600
+ ) {
601
+ if (instance.props !== finishedWork.memoizedProps) {
602
+ console.error(
603
+ 'Expected %s props to match memoized props before ' +
604
+ 'componentDidUpdate. ' +
605
+ 'This might either be because of a bug in React, or because ' +
606
+ 'a component reassigns its own `this.props`. ' +
607
+ 'Please file an issue.',
608
+ getComponentName(finishedWork.type) || 'instance',
609
+ );
610
+ }
611
+ if (instance.state !== finishedWork.memoizedState) {
612
+ console.error(
613
+ 'Expected %s state to match memoized state before ' +
614
+ 'componentDidUpdate. ' +
615
+ 'This might either be because of a bug in React, or because ' +
616
+ 'a component reassigns its own `this.state`. ' +
617
+ 'Please file an issue.',
618
+ getComponentName(finishedWork.type) || 'instance',
619
+ );
620
+ }
621
+ }
622
+ }
623
+ if (
624
+ enableProfilerTimer &&
625
+ enableProfilerCommitHooks &&
626
+ finishedWork.mode & ProfileMode
627
+ ) {
628
+ try {
629
+ startLayoutEffectTimer();
630
+ instance.componentDidUpdate(
631
+ prevProps,
632
+ prevState,
633
+ instance.__reactInternalSnapshotBeforeUpdate,
634
+ );
635
+ } finally {
636
+ recordLayoutEffectDuration(finishedWork);
637
+ }
638
+ } else {
639
+ instance.componentDidUpdate(
640
+ prevProps,
641
+ prevState,
642
+ instance.__reactInternalSnapshotBeforeUpdate,
643
+ );
644
+ }
645
+ }
646
+ }
647
+
648
+ // TODO: I think this is now always non-null by the time it reaches the
649
+ // commit phase. Consider removing the type check.
650
+ const updateQueue: UpdateQueue<
651
+ *,
652
+ > | null = (finishedWork.updateQueue: any);
653
+ if (updateQueue !== null) {
654
+ if (__DEV__) {
655
+ if (
656
+ finishedWork.type === finishedWork.elementType &&
657
+ !didWarnAboutReassigningProps
658
+ ) {
659
+ if (instance.props !== finishedWork.memoizedProps) {
660
+ console.error(
661
+ 'Expected %s props to match memoized props before ' +
662
+ 'processing the update queue. ' +
663
+ 'This might either be because of a bug in React, or because ' +
664
+ 'a component reassigns its own `this.props`. ' +
665
+ 'Please file an issue.',
666
+ getComponentName(finishedWork.type) || 'instance',
667
+ );
668
+ }
669
+ if (instance.state !== finishedWork.memoizedState) {
670
+ console.error(
671
+ 'Expected %s state to match memoized state before ' +
672
+ 'processing the update queue. ' +
673
+ 'This might either be because of a bug in React, or because ' +
674
+ 'a component reassigns its own `this.state`. ' +
675
+ 'Please file an issue.',
676
+ getComponentName(finishedWork.type) || 'instance',
677
+ );
678
+ }
679
+ }
680
+ }
681
+ // We could update instance props and state here,
682
+ // but instead we rely on them being set during last render.
683
+ // TODO: revisit this when we implement resuming.
684
+ commitUpdateQueue(finishedWork, updateQueue, instance);
685
+ }
686
+ return;
687
+ }
688
+ case HostRoot: {
689
+ // TODO: I think this is now always non-null by the time it reaches the
690
+ // commit phase. Consider removing the type check.
691
+ const updateQueue: UpdateQueue<
692
+ *,
693
+ > | null = (finishedWork.updateQueue: any);
694
+ if (updateQueue !== null) {
695
+ let instance = null;
696
+ if (finishedWork.child !== null) {
697
+ switch (finishedWork.child.tag) {
698
+ case HostComponent:
699
+ instance = getPublicInstance(finishedWork.child.stateNode);
700
+ break;
701
+ case ClassComponent:
702
+ instance = finishedWork.child.stateNode;
703
+ break;
704
+ }
705
+ }
706
+ commitUpdateQueue(finishedWork, updateQueue, instance);
707
+ }
708
+ return;
709
+ }
710
+ case HostComponent: {
711
+ const instance: Instance = finishedWork.stateNode;
712
+
713
+ // Renderers may schedule work to be done after host components are mounted
714
+ // (eg DOM renderer may schedule auto-focus for inputs and form controls).
715
+ // These effects should only be committed when components are first mounted,
716
+ // aka when there is no current/alternate.
717
+ if (current === null && finishedWork.effectTag & Update) {
718
+ const type = finishedWork.type;
719
+ const props = finishedWork.memoizedProps;
720
+ commitMount(instance, type, props, finishedWork);
721
+ }
722
+
723
+ return;
724
+ }
725
+ case HostText: {
726
+ // We have no life-cycles associated with text.
727
+ return;
728
+ }
729
+ case HostPortal: {
730
+ // We have no life-cycles associated with portals.
731
+ return;
732
+ }
733
+ case Profiler: {
734
+ if (enableProfilerTimer) {
735
+ const {onCommit, onRender} = finishedWork.memoizedProps;
736
+ const {effectDuration} = finishedWork.stateNode;
737
+
738
+ const commitTime = getCommitTime();
739
+
740
+ if (typeof onRender === 'function') {
741
+ if (enableSchedulerTracing) {
742
+ onRender(
743
+ finishedWork.memoizedProps.id,
744
+ current === null ? 'mount' : 'update',
745
+ finishedWork.actualDuration,
746
+ finishedWork.treeBaseDuration,
747
+ finishedWork.actualStartTime,
748
+ commitTime,
749
+ finishedRoot.memoizedInteractions,
750
+ );
751
+ } else {
752
+ onRender(
753
+ finishedWork.memoizedProps.id,
754
+ current === null ? 'mount' : 'update',
755
+ finishedWork.actualDuration,
756
+ finishedWork.treeBaseDuration,
757
+ finishedWork.actualStartTime,
758
+ commitTime,
759
+ );
760
+ }
761
+ }
762
+
763
+ if (enableProfilerCommitHooks) {
764
+ if (typeof onCommit === 'function') {
765
+ if (enableSchedulerTracing) {
766
+ onCommit(
767
+ finishedWork.memoizedProps.id,
768
+ current === null ? 'mount' : 'update',
769
+ effectDuration,
770
+ commitTime,
771
+ finishedRoot.memoizedInteractions,
772
+ );
773
+ } else {
774
+ onCommit(
775
+ finishedWork.memoizedProps.id,
776
+ current === null ? 'mount' : 'update',
777
+ effectDuration,
778
+ commitTime,
779
+ );
780
+ }
781
+ }
782
+
783
+ // Schedule a passive effect for this Profiler to call onPostCommit hooks.
784
+ // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
785
+ // because the effect is also where times bubble to parent Profilers.
786
+ enqueuePendingPassiveProfilerEffect(finishedWork);
787
+
788
+ // Propagate layout effect durations to the next nearest Profiler ancestor.
789
+ // Do not reset these values until the next render so DevTools has a chance to read them first.
790
+ let parentFiber = finishedWork.return;
791
+ while (parentFiber !== null) {
792
+ if (parentFiber.tag === Profiler) {
793
+ const parentStateNode = parentFiber.stateNode;
794
+ parentStateNode.effectDuration += effectDuration;
795
+ break;
796
+ }
797
+ parentFiber = parentFiber.return;
798
+ }
799
+ }
800
+ }
801
+ return;
802
+ }
803
+ case SuspenseComponent: {
804
+ commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
805
+ return;
806
+ }
807
+ case SuspenseListComponent:
808
+ case IncompleteClassComponent:
809
+ case FundamentalComponent:
810
+ case ScopeComponent:
811
+ return;
812
+ }
813
+ invariant(
814
+ false,
815
+ 'This unit of work tag should not have side-effects. This error is ' +
816
+ 'likely caused by a bug in React. Please file an issue.',
817
+ );
818
+}
819
+
820
+function hideOrUnhideAllChildren(finishedWork, isHidden) {
821
+ if (supportsMutation) {
822
+ // We only have the top Fiber that was inserted but we need to recurse down its
823
+ // children to find all the terminal nodes.
824
+ let node: Fiber = finishedWork;
825
+ while (true) {
826
+ if (node.tag === HostComponent) {
827
+ const instance = node.stateNode;
828
+ if (isHidden) {
829
+ hideInstance(instance);
830
+ } else {
831
+ unhideInstance(node.stateNode, node.memoizedProps);
832
+ }
833
+ } else if (node.tag === HostText) {
834
+ const instance = node.stateNode;
835
+ if (isHidden) {
836
+ hideTextInstance(instance);
837
+ } else {
838
+ unhideTextInstance(instance, node.memoizedProps);
839
+ }
840
+ } else if (
841
+ node.tag === SuspenseComponent &&
842
+ node.memoizedState !== null &&
843
+ node.memoizedState.dehydrated === null
844
+ ) {
845
+ // Found a nested Suspense component that timed out. Skip over the
846
+ // primary child fragment, which should remain hidden.
847
+ const fallbackChildFragment: Fiber = (node.child: any).sibling;
848
+ fallbackChildFragment.return = node;
849
+ node = fallbackChildFragment;
850
+ continue;
851
+ } else if (node.child !== null) {
852
+ node.child.return = node;
853
+ node = node.child;
854
+ continue;
855
+ }
856
+ if (node === finishedWork) {
857
+ return;
858
+ }
859
+ while (node.sibling === null) {
860
+ if (node.return === null || node.return === finishedWork) {
861
+ return;
862
+ }
863
+ node = node.return;
864
+ }
865
+ node.sibling.return = node.return;
866
+ node = node.sibling;
867
+ }
868
+ }
869
+}
870
+
871
+function commitAttachRef(finishedWork: Fiber) {
872
+ const ref = finishedWork.ref;
873
+ if (ref !== null) {
874
+ const instance = finishedWork.stateNode;
875
+ let instanceToUse;
876
+ switch (finishedWork.tag) {
877
+ case HostComponent:
878
+ instanceToUse = getPublicInstance(instance);
879
+ break;
880
+ default:
881
+ instanceToUse = instance;
882
+ }
883
+ // Moved outside to ensure DCE works with this flag
884
+ if (enableScopeAPI && finishedWork.tag === ScopeComponent) {
885
+ instanceToUse = instance.methods;
886
+ }
887
+ if (typeof ref === 'function') {
888
+ ref(instanceToUse);
889
+ } else {
890
+ if (__DEV__) {
891
+ if (!ref.hasOwnProperty('current')) {
892
+ console.error(
893
+ 'Unexpected ref object provided for %s. ' +
894
+ 'Use either a ref-setter function or React.createRef().%s',
895
+ getComponentName(finishedWork.type),
896
+ getStackByFiberInDevAndProd(finishedWork),
897
+ );
898
+ }
899
+ }
900
+
901
+ ref.current = instanceToUse;
902
+ }
903
+ }
904
+}
905
+
906
+function commitDetachRef(current: Fiber) {
907
+ const currentRef = current.ref;
908
+ if (currentRef !== null) {
909
+ if (typeof currentRef === 'function') {
910
+ currentRef(null);
911
+ } else {
912
+ currentRef.current = null;
913
+ }
914
+ }
915
+}
916
+
917
+// User-originating errors (lifecycles and refs) should not interrupt
918
+// deletion, so don't let them throw. Host-originating errors should
919
+// interrupt deletion, so it's okay
920
+function commitUnmount(
921
+ finishedRoot: FiberRoot,
922
+ current: Fiber,
923
+ renderPriorityLevel: ReactPriorityLevel,
924
+): void {
925
+ onCommitUnmount(current);
926
+
927
+ switch (current.tag) {
928
+ case FunctionComponent:
929
+ case ForwardRef:
930
+ case MemoComponent:
931
+ case SimpleMemoComponent:
932
+ case Block: {
933
+ const updateQueue: FunctionComponentUpdateQueue | null = (current.updateQueue: any);
934
+ if (updateQueue !== null) {
935
+ const lastEffect = updateQueue.lastEffect;
936
+ if (lastEffect !== null) {
937
+ const firstEffect = lastEffect.next;
938
+
939
+ if (
940
+ deferPassiveEffectCleanupDuringUnmount &&
941
+ runAllPassiveEffectDestroysBeforeCreates
942
+ ) {
943
+ let effect = firstEffect;
944
+ do {
945
+ const {destroy, tag} = effect;
946
+ if (destroy !== undefined) {
947
+ if ((tag & HookPassive) !== NoHookEffect) {
948
+ enqueuePendingPassiveHookEffectUnmount(current, effect);
949
+ } else {
950
+ if (
951
+ enableProfilerTimer &&
952
+ enableProfilerCommitHooks &&
953
+ current.mode & ProfileMode
954
+ ) {
955
+ startLayoutEffectTimer();
956
+ safelyCallDestroy(current, destroy);
957
+ recordLayoutEffectDuration(current);
958
+ } else {
959
+ safelyCallDestroy(current, destroy);
960
+ }
961
+ }
962
+ }
963
+ effect = effect.next;
964
+ } while (effect !== firstEffect);
965
+ } else {
966
+ // When the owner fiber is deleted, the destroy function of a passive
967
+ // effect hook is called during the synchronous commit phase. This is
968
+ // a concession to implementation complexity. Calling it in the
969
+ // passive effect phase (like they usually are, when dependencies
970
+ // change during an update) would require either traversing the
971
+ // children of the deleted fiber again, or including unmount effects
972
+ // as part of the fiber effect list.
973
+ //
974
+ // Because this is during the sync commit phase, we need to change
975
+ // the priority.
976
+ //
977
+ // TODO: Reconsider this implementation trade off.
978
+ const priorityLevel =
979
+ renderPriorityLevel > NormalPriority
980
+ ? NormalPriority
981
+ : renderPriorityLevel;
982
+ runWithPriority(priorityLevel, () => {
983
+ let effect = firstEffect;
984
+ do {
985
+ const {destroy, tag} = effect;
986
+ if (destroy !== undefined) {
987
+ if (
988
+ enableProfilerTimer &&
989
+ enableProfilerCommitHooks &&
990
+ current.mode & ProfileMode
991
+ ) {
992
+ if ((tag & HookPassive) !== NoHookEffect) {
993
+ safelyCallDestroy(current, destroy);
994
+ } else {
995
+ startLayoutEffectTimer();
996
+ safelyCallDestroy(current, destroy);
997
+ recordLayoutEffectDuration(current);
998
+ }
999
+ } else {
1000
+ safelyCallDestroy(current, destroy);
1001
+ }
1002
+ }
1003
+ effect = effect.next;
1004
+ } while (effect !== firstEffect);
1005
+ });
1006
+ }
1007
+ }
1008
+ }
1009
+ return;
1010
+ }
1011
+ case ClassComponent: {
1012
+ safelyDetachRef(current);
1013
+ const instance = current.stateNode;
1014
+ if (typeof instance.componentWillUnmount === 'function') {
1015
+ safelyCallComponentWillUnmount(current, instance);
1016
+ }
1017
+ return;
1018
+ }
1019
+ case HostComponent: {
1020
+ if (enableDeprecatedFlareAPI) {
1021
+ unmountDeprecatedResponderListeners(current);
1022
+ }
1023
+ if (enableDeprecatedFlareAPI || enableUseEventAPI) {
1024
+ beforeRemoveInstance(current.stateNode);
1025
+ }
1026
+ safelyDetachRef(current);
1027
+ return;
1028
+ }
1029
+ case HostPortal: {
1030
+ // TODO: this is recursive.
1031
+ // We are also not using this parent because
1032
+ // the portal will get pushed immediately.
1033
+ if (supportsMutation) {
1034
+ unmountHostComponents(finishedRoot, current, renderPriorityLevel);
1035
+ } else if (supportsPersistence) {
1036
+ emptyPortalContainer(current);
1037
+ }
1038
+ return;
1039
+ }
1040
+ case FundamentalComponent: {
1041
+ if (enableFundamentalAPI) {
1042
+ const fundamentalInstance = current.stateNode;
1043
+ if (fundamentalInstance !== null) {
1044
+ unmountFundamentalComponent(fundamentalInstance);
1045
+ current.stateNode = null;
1046
+ }
1047
+ }
1048
+ return;
1049
+ }
1050
+ case DehydratedFragment: {
1051
+ if (enableSuspenseCallback) {
1052
+ const hydrationCallbacks = finishedRoot.hydrationCallbacks;
1053
+ if (hydrationCallbacks !== null) {
1054
+ const onDeleted = hydrationCallbacks.onDeleted;
1055
+ if (onDeleted) {
1056
+ onDeleted((current.stateNode: SuspenseInstance));
1057
+ }
1058
+ }
1059
+ }
1060
+ return;
1061
+ }
1062
+ case ScopeComponent: {
1063
+ if (enableDeprecatedFlareAPI) {
1064
+ unmountDeprecatedResponderListeners(current);
1065
+ }
1066
+ if (enableScopeAPI) {
1067
+ safelyDetachRef(current);
1068
+ }
1069
+ return;
1070
+ }
1071
+ }
1072
+}
1073
+
1074
+function commitNestedUnmounts(
1075
+ finishedRoot: FiberRoot,
1076
+ root: Fiber,
1077
+ renderPriorityLevel: ReactPriorityLevel,
1078
+): void {
1079
+ // While we're inside a removed host node we don't want to call
1080
+ // removeChild on the inner nodes because they're removed by the top
1081
+ // call anyway. We also want to call componentWillUnmount on all
1082
+ // composites before this host node is removed from the tree. Therefore
1083
+ // we do an inner loop while we're still inside the host node.
1084
+ let node: Fiber = root;
1085
+ while (true) {
1086
+ commitUnmount(finishedRoot, node, renderPriorityLevel);
1087
+ // Visit children because they may contain more composite or host nodes.
1088
+ // Skip portals because commitUnmount() currently visits them recursively.
1089
+ if (
1090
+ node.child !== null &&
1091
+ // If we use mutation we drill down into portals using commitUnmount above.
1092
+ // If we don't use mutation we drill down into portals here instead.
1093
+ (!supportsMutation || node.tag !== HostPortal)
1094
+ ) {
1095
+ node.child.return = node;
1096
+ node = node.child;
1097
+ continue;
1098
+ }
1099
+ if (node === root) {
1100
+ return;
1101
+ }
1102
+ while (node.sibling === null) {
1103
+ if (node.return === null || node.return === root) {
1104
+ return;
1105
+ }
1106
+ node = node.return;
1107
+ }
1108
+ node.sibling.return = node.return;
1109
+ node = node.sibling;
1110
+ }
1111
+}
1112
+
1113
+function detachFiber(fiber: Fiber) {
1114
+ // Cut off the return pointers to disconnect it from the tree. Ideally, we
1115
+ // should clear the child pointer of the parent alternate to let this
1116
+ // get GC:ed but we don't know which for sure which parent is the current
1117
+ // one so we'll settle for GC:ing the subtree of this child. This child
1118
+ // itself will be GC:ed when the parent updates the next time.
1119
+ fiber.return = null;
1120
+ fiber.child = null;
1121
+ fiber.memoizedState = null;
1122
+ fiber.updateQueue = null;
1123
+ fiber.dependencies = null;
1124
+ fiber.alternate = null;
1125
+ fiber.firstEffect = null;
1126
+ fiber.lastEffect = null;
1127
+ fiber.pendingProps = null;
1128
+ fiber.memoizedProps = null;
1129
+ fiber.stateNode = null;
1130
+}
1131
+
1132
+function emptyPortalContainer(current: Fiber) {
1133
+ if (!supportsPersistence) {
1134
+ return;
1135
+ }
1136
+
1137
+ const portal: {
1138
+ containerInfo: Container,
1139
+ pendingChildren: ChildSet,
1140
+ ...
1141
+ } = current.stateNode;
1142
+ const {containerInfo} = portal;
1143
+ const emptyChildSet = createContainerChildSet(containerInfo);
1144
+ replaceContainerChildren(containerInfo, emptyChildSet);
1145
+}
1146
+
1147
+function commitContainer(finishedWork: Fiber) {
1148
+ if (!supportsPersistence) {
1149
+ return;
1150
+ }
1151
+
1152
+ switch (finishedWork.tag) {
1153
+ case ClassComponent:
1154
+ case HostComponent:
1155
+ case HostText:
1156
+ case FundamentalComponent: {
1157
+ return;
1158
+ }
1159
+ case HostRoot:
1160
+ case HostPortal: {
1161
+ const portalOrRoot: {
1162
+ containerInfo: Container,
1163
+ pendingChildren: ChildSet,
1164
+ ...
1165
+ } = finishedWork.stateNode;
1166
+ const {containerInfo, pendingChildren} = portalOrRoot;
1167
+ replaceContainerChildren(containerInfo, pendingChildren);
1168
+ return;
1169
+ }
1170
+ }
1171
+ invariant(
1172
+ false,
1173
+ 'This unit of work tag should not have side-effects. This error is ' +
1174
+ 'likely caused by a bug in React. Please file an issue.',
1175
+ );
1176
+}
1177
+
1178
+function getHostParentFiber(fiber: Fiber): Fiber {
1179
+ let parent = fiber.return;
1180
+ while (parent !== null) {
1181
+ if (isHostParent(parent)) {
1182
+ return parent;
1183
+ }
1184
+ parent = parent.return;
1185
+ }
1186
+ invariant(
1187
+ false,
1188
+ 'Expected to find a host parent. This error is likely caused by a bug ' +
1189
+ 'in React. Please file an issue.',
1190
+ );
1191
+}
1192
+
1193
+function isHostParent(fiber: Fiber): boolean {
1194
+ return (
1195
+ fiber.tag === HostComponent ||
1196
+ fiber.tag === HostRoot ||
1197
+ fiber.tag === HostPortal
1198
+ );
1199
+}
1200
+
1201
+function getHostSibling(fiber: Fiber): ?Instance {
1202
+ // We're going to search forward into the tree until we find a sibling host
1203
+ // node. Unfortunately, if multiple insertions are done in a row we have to
1204
+ // search past them. This leads to exponential search for the next sibling.
1205
+ // TODO: Find a more efficient way to do this.
1206
+ let node: Fiber = fiber;
1207
+ siblings: while (true) {
1208
+ // If we didn't find anything, let's try the next sibling.
1209
+ while (node.sibling === null) {
1210
+ if (node.return === null || isHostParent(node.return)) {
1211
+ // If we pop out of the root or hit the parent the fiber we are the
1212
+ // last sibling.
1213
+ return null;
1214
+ }
1215
+ node = node.return;
1216
+ }
1217
+ node.sibling.return = node.return;
1218
+ node = node.sibling;
1219
+ while (
1220
+ node.tag !== HostComponent &&
1221
+ node.tag !== HostText &&
1222
+ node.tag !== DehydratedFragment
1223
+ ) {
1224
+ // If it is not host node and, we might have a host node inside it.
1225
+ // Try to search down until we find one.
1226
+ if (node.effectTag & Placement) {
1227
+ // If we don't have a child, try the siblings instead.
1228
+ continue siblings;
1229
+ }
1230
+ // If we don't have a child, try the siblings instead.
1231
+ // We also skip portals because they are not part of this host tree.
1232
+ if (node.child === null || node.tag === HostPortal) {
1233
+ continue siblings;
1234
+ } else {
1235
+ node.child.return = node;
1236
+ node = node.child;
1237
+ }
1238
+ }
1239
+ // Check if this host node is stable or about to be placed.
1240
+ if (!(node.effectTag & Placement)) {
1241
+ // Found it!
1242
+ return node.stateNode;
1243
+ }
1244
+ }
1245
+}
1246
+
1247
+function commitPlacement(finishedWork: Fiber): void {
1248
+ if (!supportsMutation) {
1249
+ return;
1250
+ }
1251
+
1252
+ // Recursively insert all host nodes into the parent.
1253
+ const parentFiber = getHostParentFiber(finishedWork);
1254
+
1255
+ // Note: these two variables *must* always be updated together.
1256
+ let parent;
1257
+ let isContainer;
1258
+ const parentStateNode = parentFiber.stateNode;
1259
+ switch (parentFiber.tag) {
1260
+ case HostComponent:
1261
+ parent = parentStateNode;
1262
+ isContainer = false;
1263
+ break;
1264
+ case HostRoot:
1265
+ parent = parentStateNode.containerInfo;
1266
+ isContainer = true;
1267
+ break;
1268
+ case HostPortal:
1269
+ parent = parentStateNode.containerInfo;
1270
+ isContainer = true;
1271
+ break;
1272
+ case FundamentalComponent:
1273
+ if (enableFundamentalAPI) {
1274
+ parent = parentStateNode.instance;
1275
+ isContainer = false;
1276
+ }
1277
+ // eslint-disable-next-line-no-fallthrough
1278
+ default:
1279
+ invariant(
1280
+ false,
1281
+ 'Invalid host parent fiber. This error is likely caused by a bug ' +
1282
+ 'in React. Please file an issue.',
1283
+ );
1284
+ }
1285
+ if (parentFiber.effectTag & ContentReset) {
1286
+ // Reset the text content of the parent before doing any insertions
1287
+ resetTextContent(parent);
1288
+ // Clear ContentReset from the effect tag
1289
+ parentFiber.effectTag &= ~ContentReset;
1290
+ }
1291
+
1292
+ const before = getHostSibling(finishedWork);
1293
+ // We only have the top Fiber that was inserted but we need to recurse down its
1294
+ // children to find all the terminal nodes.
1295
+ if (isContainer) {
1296
+ insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
1297
+ } else {
1298
+ insertOrAppendPlacementNode(finishedWork, before, parent);
1299
+ }
1300
+}
1301
+
1302
+function insertOrAppendPlacementNodeIntoContainer(
1303
+ node: Fiber,
1304
+ before: ?Instance,
1305
+ parent: Container,
1306
+): void {
1307
+ const {tag} = node;
1308
+ const isHost = tag === HostComponent || tag === HostText;
1309
+ if (isHost || (enableFundamentalAPI && tag === FundamentalComponent)) {
1310
+ const stateNode = isHost ? node.stateNode : node.stateNode.instance;
1311
+ if (before) {
1312
+ insertInContainerBefore(parent, stateNode, before);
1313
+ } else {
1314
+ appendChildToContainer(parent, stateNode);
1315
+ }
1316
+ } else if (tag === HostPortal) {
1317
+ // If the insertion itself is a portal, then we don't want to traverse
1318
+ // down its children. Instead, we'll get insertions from each child in
1319
+ // the portal directly.
1320
+ } else {
1321
+ const child = node.child;
1322
+ if (child !== null) {
1323
+ insertOrAppendPlacementNodeIntoContainer(child, before, parent);
1324
+ let sibling = child.sibling;
1325
+ while (sibling !== null) {
1326
+ insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
1327
+ sibling = sibling.sibling;
1328
+ }
1329
+ }
1330
+ }
1331
+}
1332
+
1333
+function insertOrAppendPlacementNode(
1334
+ node: Fiber,
1335
+ before: ?Instance,
1336
+ parent: Instance,
1337
+): void {
1338
+ const {tag} = node;
1339
+ const isHost = tag === HostComponent || tag === HostText;
1340
+ if (isHost || (enableFundamentalAPI && tag === FundamentalComponent)) {
1341
+ const stateNode = isHost ? node.stateNode : node.stateNode.instance;
1342
+ if (before) {
1343
+ insertBefore(parent, stateNode, before);
1344
+ } else {
1345
+ appendChild(parent, stateNode);
1346
+ }
1347
+ } else if (tag === HostPortal) {
1348
+ // If the insertion itself is a portal, then we don't want to traverse
1349
+ // down its children. Instead, we'll get insertions from each child in
1350
+ // the portal directly.
1351
+ } else {
1352
+ const child = node.child;
1353
+ if (child !== null) {
1354
+ insertOrAppendPlacementNode(child, before, parent);
1355
+ let sibling = child.sibling;
1356
+ while (sibling !== null) {
1357
+ insertOrAppendPlacementNode(sibling, before, parent);
1358
+ sibling = sibling.sibling;
1359
+ }
1360
+ }
1361
+ }
1362
+}
1363
+
1364
+function unmountHostComponents(
1365
+ finishedRoot,
1366
+ current,
1367
+ renderPriorityLevel,
1368
+): void {
1369
+ // We only have the top Fiber that was deleted but we need to recurse down its
1370
+ // children to find all the terminal nodes.
1371
+ let node: Fiber = current;
1372
+
1373
+ // Each iteration, currentParent is populated with node's host parent if not
1374
+ // currentParentIsValid.
1375
+ let currentParentIsValid = false;
1376
+
1377
+ // Note: these two variables *must* always be updated together.
1378
+ let currentParent;
1379
+ let currentParentIsContainer;
1380
+
1381
+ while (true) {
1382
+ if (!currentParentIsValid) {
1383
+ let parent = node.return;
1384
+ findParent: while (true) {
1385
+ invariant(
1386
+ parent !== null,
1387
+ 'Expected to find a host parent. This error is likely caused by ' +
1388
+ 'a bug in React. Please file an issue.',
1389
+ );
1390
+ const parentStateNode = parent.stateNode;
1391
+ switch (parent.tag) {
1392
+ case HostComponent:
1393
+ currentParent = parentStateNode;
1394
+ currentParentIsContainer = false;
1395
+ break findParent;
1396
+ case HostRoot:
1397
+ currentParent = parentStateNode.containerInfo;
1398
+ currentParentIsContainer = true;
1399
+ break findParent;
1400
+ case HostPortal:
1401
+ currentParent = parentStateNode.containerInfo;
1402
+ currentParentIsContainer = true;
1403
+ break findParent;
1404
+ case FundamentalComponent:
1405
+ if (enableFundamentalAPI) {
1406
+ currentParent = parentStateNode.instance;
1407
+ currentParentIsContainer = false;
1408
+ }
1409
+ }
1410
+ parent = parent.return;
1411
+ }
1412
+ currentParentIsValid = true;
1413
+ }
1414
+
1415
+ if (node.tag === HostComponent || node.tag === HostText) {
1416
+ commitNestedUnmounts(finishedRoot, node, renderPriorityLevel);
1417
+ // After all the children have unmounted, it is now safe to remove the
1418
+ // node from the tree.
1419
+ if (currentParentIsContainer) {
1420
+ removeChildFromContainer(
1421
+ ((currentParent: any): Container),
1422
+ (node.stateNode: Instance | TextInstance),
1423
+ );
1424
+ } else {
1425
+ removeChild(
1426
+ ((currentParent: any): Instance),
1427
+ (node.stateNode: Instance | TextInstance),
1428
+ );
1429
+ }
1430
+ // Don't visit children because we already visited them.
1431
+ } else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
1432
+ const fundamentalNode = node.stateNode.instance;
1433
+ commitNestedUnmounts(finishedRoot, node, renderPriorityLevel);
1434
+ // After all the children have unmounted, it is now safe to remove the
1435
+ // node from the tree.
1436
+ if (currentParentIsContainer) {
1437
+ removeChildFromContainer(
1438
+ ((currentParent: any): Container),
1439
+ (fundamentalNode: Instance),
1440
+ );
1441
+ } else {
1442
+ removeChild(
1443
+ ((currentParent: any): Instance),
1444
+ (fundamentalNode: Instance),
1445
+ );
1446
+ }
1447
+ } else if (
1448
+ enableSuspenseServerRenderer &&
1449
+ node.tag === DehydratedFragment
1450
+ ) {
1451
+ if (enableSuspenseCallback) {
1452
+ const hydrationCallbacks = finishedRoot.hydrationCallbacks;
1453
+ if (hydrationCallbacks !== null) {
1454
+ const onDeleted = hydrationCallbacks.onDeleted;
1455
+ if (onDeleted) {
1456
+ onDeleted((node.stateNode: SuspenseInstance));
1457
+ }
1458
+ }
1459
+ }
1460
+
1461
+ // Delete the dehydrated suspense boundary and all of its content.
1462
+ if (currentParentIsContainer) {
1463
+ clearSuspenseBoundaryFromContainer(
1464
+ ((currentParent: any): Container),
1465
+ (node.stateNode: SuspenseInstance),
1466
+ );
1467
+ } else {
1468
+ clearSuspenseBoundary(
1469
+ ((currentParent: any): Instance),
1470
+ (node.stateNode: SuspenseInstance),
1471
+ );
1472
+ }
1473
+ } else if (node.tag === HostPortal) {
1474
+ if (node.child !== null) {
1475
+ // When we go into a portal, it becomes the parent to remove from.
1476
+ // We will reassign it back when we pop the portal on the way up.
1477
+ currentParent = node.stateNode.containerInfo;
1478
+ currentParentIsContainer = true;
1479
+ // Visit children because portals might contain host components.
1480
+ node.child.return = node;
1481
+ node = node.child;
1482
+ continue;
1483
+ }
1484
+ } else {
1485
+ commitUnmount(finishedRoot, node, renderPriorityLevel);
1486
+ // Visit children because we may find more host components below.
1487
+ if (node.child !== null) {
1488
+ node.child.return = node;
1489
+ node = node.child;
1490
+ continue;
1491
+ }
1492
+ }
1493
+ if (node === current) {
1494
+ return;
1495
+ }
1496
+ while (node.sibling === null) {
1497
+ if (node.return === null || node.return === current) {
1498
+ return;
1499
+ }
1500
+ node = node.return;
1501
+ if (node.tag === HostPortal) {
1502
+ // When we go out of the portal, we need to restore the parent.
1503
+ // Since we don't keep a stack of them, we will search for it.
1504
+ currentParentIsValid = false;
1505
+ }
1506
+ }
1507
+ node.sibling.return = node.return;
1508
+ node = node.sibling;
1509
+ }
1510
+}
1511
+
1512
+function commitDeletion(
1513
+ finishedRoot: FiberRoot,
1514
+ current: Fiber,
1515
+ renderPriorityLevel: ReactPriorityLevel,
1516
+): void {
1517
+ if (supportsMutation) {
1518
+ // Recursively delete all host nodes from the parent.
1519
+ // Detach refs and call componentWillUnmount() on the whole subtree.
1520
+ unmountHostComponents(finishedRoot, current, renderPriorityLevel);
1521
+ } else {
1522
+ // Detach refs and call componentWillUnmount() on the whole subtree.
1523
+ commitNestedUnmounts(finishedRoot, current, renderPriorityLevel);
1524
+ }
1525
+ const alternate = current.alternate;
1526
+ detachFiber(current);
1527
+ if (alternate !== null) {
1528
+ detachFiber(alternate);
1529
+ }
1530
+}
1531
+
1532
+function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1533
+ if (!supportsMutation) {
1534
+ switch (finishedWork.tag) {
1535
+ case FunctionComponent:
1536
+ case ForwardRef:
1537
+ case MemoComponent:
1538
+ case SimpleMemoComponent:
1539
+ case Block: {
1540
+ // Layout effects are destroyed during the mutation phase so that all
1541
+ // destroy functions for all fibers are called before any create functions.
1542
+ // This prevents sibling component effects from interfering with each other,
1543
+ // e.g. a destroy function in one component should never override a ref set
1544
+ // by a create function in another component during the same commit.
1545
+ if (
1546
+ enableProfilerTimer &&
1547
+ enableProfilerCommitHooks &&
1548
+ finishedWork.mode & ProfileMode
1549
+ ) {
1550
+ try {
1551
+ startLayoutEffectTimer();
1552
+ commitHookEffectListUnmount(
1553
+ HookLayout | HookHasEffect,
1554
+ finishedWork,
1555
+ );
1556
+ } finally {
1557
+ recordLayoutEffectDuration(finishedWork);
1558
+ }
1559
+ } else {
1560
+ commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
1561
+ }
1562
+ return;
1563
+ }
1564
+ case Profiler: {
1565
+ return;
1566
+ }
1567
+ case SuspenseComponent: {
1568
+ commitSuspenseComponent(finishedWork);
1569
+ attachSuspenseRetryListeners(finishedWork);
1570
+ return;
1571
+ }
1572
+ case SuspenseListComponent: {
1573
+ attachSuspenseRetryListeners(finishedWork);
1574
+ return;
1575
+ }
1576
+ case HostRoot: {
1577
+ if (supportsHydration) {
1578
+ const root: FiberRoot = finishedWork.stateNode;
1579
+ if (root.hydrate) {
1580
+ // We've just hydrated. No need to hydrate again.
1581
+ root.hydrate = false;
1582
+ commitHydratedContainer(root.containerInfo);
1583
+ }
1584
+ }
1585
+ break;
1586
+ }
1587
+ }
1588
+
1589
+ commitContainer(finishedWork);
1590
+ return;
1591
+ }
1592
+
1593
+ switch (finishedWork.tag) {
1594
+ case FunctionComponent:
1595
+ case ForwardRef:
1596
+ case MemoComponent:
1597
+ case SimpleMemoComponent:
1598
+ case Block: {
1599
+ // Layout effects are destroyed during the mutation phase so that all
1600
+ // destroy functions for all fibers are called before any create functions.
1601
+ // This prevents sibling component effects from interfering with each other,
1602
+ // e.g. a destroy function in one component should never override a ref set
1603
+ // by a create function in another component during the same commit.
1604
+ if (
1605
+ enableProfilerTimer &&
1606
+ enableProfilerCommitHooks &&
1607
+ finishedWork.mode & ProfileMode
1608
+ ) {
1609
+ try {
1610
+ startLayoutEffectTimer();
1611
+ commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
1612
+ } finally {
1613
+ recordLayoutEffectDuration(finishedWork);
1614
+ }
1615
+ } else {
1616
+ commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
1617
+ }
1618
+ return;
1619
+ }
1620
+ case ClassComponent: {
1621
+ return;
1622
+ }
1623
+ case HostComponent: {
1624
+ const instance: Instance = finishedWork.stateNode;
1625
+ if (instance != null) {
1626
+ // Commit the work prepared earlier.
1627
+ const newProps = finishedWork.memoizedProps;
1628
+ // For hydration we reuse the update path but we treat the oldProps
1629
+ // as the newProps. The updatePayload will contain the real change in
1630
+ // this case.
1631
+ const oldProps = current !== null ? current.memoizedProps : newProps;
1632
+ const type = finishedWork.type;
1633
+ // TODO: Type the updateQueue to be specific to host components.
1634
+ const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
1635
+ finishedWork.updateQueue = null;
1636
+ if (updatePayload !== null) {
1637
+ commitUpdate(
1638
+ instance,
1639
+ updatePayload,
1640
+ type,
1641
+ oldProps,
1642
+ newProps,
1643
+ finishedWork,
1644
+ );
1645
+ }
1646
+ if (enableDeprecatedFlareAPI) {
1647
+ const prevListeners = oldProps.DEPRECATED_flareListeners;
1648
+ const nextListeners = newProps.DEPRECATED_flareListeners;
1649
+ if (prevListeners !== nextListeners) {
1650
+ updateDeprecatedEventListeners(nextListeners, finishedWork, null);
1651
+ }
1652
+ }
1653
+ }
1654
+ return;
1655
+ }
1656
+ case HostText: {
1657
+ invariant(
1658
+ finishedWork.stateNode !== null,
1659
+ 'This should have a text node initialized. This error is likely ' +
1660
+ 'caused by a bug in React. Please file an issue.',
1661
+ );
1662
+ const textInstance: TextInstance = finishedWork.stateNode;
1663
+ const newText: string = finishedWork.memoizedProps;
1664
+ // For hydration we reuse the update path but we treat the oldProps
1665
+ // as the newProps. The updatePayload will contain the real change in
1666
+ // this case.
1667
+ const oldText: string =
1668
+ current !== null ? current.memoizedProps : newText;
1669
+ commitTextUpdate(textInstance, oldText, newText);
1670
+ return;
1671
+ }
1672
+ case HostRoot: {
1673
+ if (supportsHydration) {
1674
+ const root: FiberRoot = finishedWork.stateNode;
1675
+ if (root.hydrate) {
1676
+ // We've just hydrated. No need to hydrate again.
1677
+ root.hydrate = false;
1678
+ commitHydratedContainer(root.containerInfo);
1679
+ }
1680
+ }
1681
+ return;
1682
+ }
1683
+ case Profiler: {
1684
+ return;
1685
+ }
1686
+ case SuspenseComponent: {
1687
+ commitSuspenseComponent(finishedWork);
1688
+ attachSuspenseRetryListeners(finishedWork);
1689
+ return;
1690
+ }
1691
+ case SuspenseListComponent: {
1692
+ attachSuspenseRetryListeners(finishedWork);
1693
+ return;
1694
+ }
1695
+ case IncompleteClassComponent: {
1696
+ return;
1697
+ }
1698
+ case FundamentalComponent: {
1699
+ if (enableFundamentalAPI) {
1700
+ const fundamentalInstance = finishedWork.stateNode;
1701
+ updateFundamentalComponent(fundamentalInstance);
1702
+ return;
1703
+ }
1704
+ break;
1705
+ }
1706
+ case ScopeComponent: {
1707
+ if (enableScopeAPI) {
1708
+ const scopeInstance = finishedWork.stateNode;
1709
+ scopeInstance.fiber = finishedWork;
1710
+ if (enableDeprecatedFlareAPI) {
1711
+ const newProps = finishedWork.memoizedProps;
1712
+ const oldProps = current !== null ? current.memoizedProps : newProps;
1713
+ const prevListeners = oldProps.DEPRECATED_flareListeners;
1714
+ const nextListeners = newProps.DEPRECATED_flareListeners;
1715
+ if (prevListeners !== nextListeners || current === null) {
1716
+ updateDeprecatedEventListeners(nextListeners, finishedWork, null);
1717
+ }
1718
+ }
1719
+ return;
1720
+ }
1721
+ break;
1722
+ }
1723
+ }
1724
+ invariant(
1725
+ false,
1726
+ 'This unit of work tag should not have side-effects. This error is ' +
1727
+ 'likely caused by a bug in React. Please file an issue.',
1728
+ );
1729
+}
1730
+
1731
+function commitSuspenseComponent(finishedWork: Fiber) {
1732
+ const newState: SuspenseState | null = finishedWork.memoizedState;
1733
+
1734
+ let newDidTimeout;
1735
+ let primaryChildParent = finishedWork;
1736
+ if (newState === null) {
1737
+ newDidTimeout = false;
1738
+ } else {
1739
+ newDidTimeout = true;
1740
+ primaryChildParent = finishedWork.child;
1741
+ markCommitTimeOfFallback();
1742
+ }
1743
+
1744
+ if (supportsMutation && primaryChildParent !== null) {
1745
+ hideOrUnhideAllChildren(primaryChildParent, newDidTimeout);
1746
+ }
1747
+
1748
+ if (enableSuspenseCallback && newState !== null) {
1749
+ const suspenseCallback = finishedWork.memoizedProps.suspenseCallback;
1750
+ if (typeof suspenseCallback === 'function') {
1751
+ const wakeables: Set<Wakeable> | null = (finishedWork.updateQueue: any);
1752
+ if (wakeables !== null) {
1753
+ suspenseCallback(new Set(wakeables));
1754
+ }
1755
+ } else if (__DEV__) {
1756
+ if (suspenseCallback !== undefined) {
1757
+ console.error('Unexpected type for suspenseCallback.');
1758
+ }
1759
+ }
1760
+ }
1761
+}
1762
+
1763
+function commitSuspenseHydrationCallbacks(
1764
+ finishedRoot: FiberRoot,
1765
+ finishedWork: Fiber,
1766
+) {
1767
+ if (!supportsHydration) {
1768
+ return;
1769
+ }
1770
+ const newState: SuspenseState | null = finishedWork.memoizedState;
1771
+ if (newState === null) {
1772
+ const current = finishedWork.alternate;
1773
+ if (current !== null) {
1774
+ const prevState: SuspenseState | null = current.memoizedState;
1775
+ if (prevState !== null) {
1776
+ const suspenseInstance = prevState.dehydrated;
1777
+ if (suspenseInstance !== null) {
1778
+ commitHydratedSuspenseInstance(suspenseInstance);
1779
+ if (enableSuspenseCallback) {
1780
+ const hydrationCallbacks = finishedRoot.hydrationCallbacks;
1781
+ if (hydrationCallbacks !== null) {
1782
+ const onHydrated = hydrationCallbacks.onHydrated;
1783
+ if (onHydrated) {
1784
+ onHydrated(suspenseInstance);
1785
+ }
1786
+ }
1787
+ }
1788
+ }
1789
+ }
1790
+ }
1791
+ }
1792
+}
1793
+
1794
+function attachSuspenseRetryListeners(finishedWork: Fiber) {
1795
+ // If this boundary just timed out, then it will have a set of wakeables.
1796
+ // For each wakeable, attach a listener so that when it resolves, React
1797
+ // attempts to re-render the boundary in the primary (pre-timeout) state.
1798
+ const wakeables: Set<Wakeable> | null = (finishedWork.updateQueue: any);
1799
+ if (wakeables !== null) {
1800
+ finishedWork.updateQueue = null;
1801
+ let retryCache = finishedWork.stateNode;
1802
+ if (retryCache === null) {
1803
+ retryCache = finishedWork.stateNode = new PossiblyWeakSet();
1804
+ }
1805
+ wakeables.forEach(wakeable => {
1806
+ // Memoize using the boundary fiber to prevent redundant listeners.
1807
+ let retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
1808
+ if (!retryCache.has(wakeable)) {
1809
+ if (enableSchedulerTracing) {
1810
+ if (wakeable.__reactDoNotTraceInteractions !== true) {
1811
+ retry = Schedule_tracing_wrap(retry);
1812
+ }
1813
+ }
1814
+ retryCache.add(wakeable);
1815
+ wakeable.then(retry, retry);
1816
+ }
1817
+ });
1818
+ }
1819
+}
1820
+
1821
+function commitResetTextContent(current: Fiber) {
1822
+ if (!supportsMutation) {
1823
+ return;
1824
+ }
1825
+ resetTextContent(current.stateNode);
1826
+}
1827
+
1828
+export {
1829
+ commitBeforeMutationLifeCycles,
1830
+ commitResetTextContent,
1831
+ commitPlacement,
1832
+ commitDeletion,
1833
+ commitWork,
1834
+ commitLifeCycles,
1835
+ commitAttachRef,
1836
+ commitDetachRef,
1837
+};
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
new
+1314
@@ -0,0 +1,1314 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactInternalTypes';
11
+import type {ExpirationTime} from './ReactFiberExpirationTime';
12
+import type {
13
+ ReactFundamentalComponentInstance,
14
+ ReactScopeInstance,
15
+} from 'shared/ReactTypes';
16
+import type {FiberRoot} from './ReactInternalTypes';
17
+import type {
18
+ Instance,
19
+ Type,
20
+ Props,
21
+ Container,
22
+ ChildSet,
23
+} from './ReactFiberHostConfig';
24
+import type {
25
+ SuspenseState,
26
+ SuspenseListRenderState,
27
+} from './ReactFiberSuspenseComponent.new';
28
+import type {SuspenseContext} from './ReactFiberSuspenseContext.new';
29
+import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.new';
30
+
31
+import {now} from './SchedulerWithReactIntegration.new';
32
+
33
+import {
34
+ IndeterminateComponent,
35
+ FunctionComponent,
36
+ ClassComponent,
37
+ HostRoot,
38
+ HostComponent,
39
+ HostText,
40
+ HostPortal,
41
+ ContextProvider,
42
+ ContextConsumer,
43
+ ForwardRef,
44
+ Fragment,
45
+ Mode,
46
+ Profiler,
47
+ SuspenseComponent,
48
+ SuspenseListComponent,
49
+ MemoComponent,
50
+ SimpleMemoComponent,
51
+ LazyComponent,
52
+ IncompleteClassComponent,
53
+ FundamentalComponent,
54
+ ScopeComponent,
55
+ Block,
56
+} from './ReactWorkTags';
57
+import {NoMode, BlockingMode} from './ReactTypeOfMode';
58
+import {
59
+ Ref,
60
+ Update,
61
+ NoEffect,
62
+ DidCapture,
63
+ Deletion,
64
+} from './ReactSideEffectTags';
65
+import invariant from 'shared/invariant';
66
+
67
+import {
68
+ createInstance,
69
+ createTextInstance,
70
+ appendInitialChild,
71
+ finalizeInitialChildren,
72
+ prepareUpdate,
73
+ supportsMutation,
74
+ supportsPersistence,
75
+ cloneInstance,
76
+ cloneHiddenInstance,
77
+ cloneHiddenTextInstance,
78
+ createContainerChildSet,
79
+ appendChildToContainerChildSet,
80
+ finalizeContainerChildren,
81
+ getFundamentalComponentInstance,
82
+ mountFundamentalComponent,
83
+ cloneFundamentalInstance,
84
+ shouldUpdateFundamentalComponent,
85
+} from './ReactFiberHostConfig';
86
+import {
87
+ getRootHostContainer,
88
+ popHostContext,
89
+ getHostContext,
90
+ popHostContainer,
91
+} from './ReactFiberHostContext.new';
92
+import {
93
+ suspenseStackCursor,
94
+ InvisibleParentSuspenseContext,
95
+ hasSuspenseContext,
96
+ popSuspenseContext,
97
+ pushSuspenseContext,
98
+ setShallowSuspenseContext,
99
+ ForceSuspenseFallback,
100
+ setDefaultShallowSuspenseContext,
101
+} from './ReactFiberSuspenseContext.new';
102
+import {findFirstSuspended} from './ReactFiberSuspenseComponent.new';
103
+import {
104
+ isContextProvider as isLegacyContextProvider,
105
+ popContext as popLegacyContext,
106
+ popTopLevelContextObject as popTopLevelLegacyContextObject,
107
+} from './ReactFiberContext.new';
108
+import {popProvider} from './ReactFiberNewContext.new';
109
+import {
110
+ prepareToHydrateHostInstance,
111
+ prepareToHydrateHostTextInstance,
112
+ prepareToHydrateHostSuspenseInstance,
113
+ popHydrationState,
114
+ resetHydrationState,
115
+} from './ReactFiberHydrationContext.new';
116
+import {
117
+ enableSchedulerTracing,
118
+ enableSuspenseCallback,
119
+ enableSuspenseServerRenderer,
120
+ enableDeprecatedFlareAPI,
121
+ enableFundamentalAPI,
122
+ enableScopeAPI,
123
+ enableBlocksAPI,
124
+} from 'shared/ReactFeatureFlags';
125
+import {
126
+ markSpawnedWork,
127
+ renderDidSuspend,
128
+ renderDidSuspendDelayIfPossible,
129
+ renderHasNotSuspendedYet,
130
+} from './ReactFiberWorkLoop.new';
131
+import {createFundamentalStateInstance} from './ReactFiberFundamental.new';
132
+import {Never} from './ReactFiberExpirationTime';
133
+import {resetChildFibers} from './ReactChildFiber.new';
134
+import {updateDeprecatedEventListeners} from './ReactFiberDeprecatedEvents.new';
135
+import {createScopeMethods} from './ReactFiberScope.new';
136
+
137
+function markUpdate(workInProgress: Fiber) {
138
+ // Tag the fiber with an update effect. This turns a Placement into
139
+ // a PlacementAndUpdate.
140
+ workInProgress.effectTag |= Update;
141
+}
142
+
143
+function markRef(workInProgress: Fiber) {
144
+ workInProgress.effectTag |= Ref;
145
+}
146
+
147
+let appendAllChildren;
148
+let updateHostContainer;
149
+let updateHostComponent;
150
+let updateHostText;
151
+if (supportsMutation) {
152
+ // Mutation mode
153
+
154
+ appendAllChildren = function(
155
+ parent: Instance,
156
+ workInProgress: Fiber,
157
+ needsVisibilityToggle: boolean,
158
+ isHidden: boolean,
159
+ ) {
160
+ // We only have the top Fiber that was created but we need recurse down its
161
+ // children to find all the terminal nodes.
162
+ let node = workInProgress.child;
163
+ while (node !== null) {
164
+ if (node.tag === HostComponent || node.tag === HostText) {
165
+ appendInitialChild(parent, node.stateNode);
166
+ } else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
167
+ appendInitialChild(parent, node.stateNode.instance);
168
+ } else if (node.tag === HostPortal) {
169
+ // If we have a portal child, then we don't want to traverse
170
+ // down its children. Instead, we'll get insertions from each child in
171
+ // the portal directly.
172
+ } else if (node.child !== null) {
173
+ node.child.return = node;
174
+ node = node.child;
175
+ continue;
176
+ }
177
+ if (node === workInProgress) {
178
+ return;
179
+ }
180
+ while (node.sibling === null) {
181
+ if (node.return === null || node.return === workInProgress) {
182
+ return;
183
+ }
184
+ node = node.return;
185
+ }
186
+ node.sibling.return = node.return;
187
+ node = node.sibling;
188
+ }
189
+ };
190
+
191
+ updateHostContainer = function(workInProgress: Fiber) {
192
+ // Noop
193
+ };
194
+ updateHostComponent = function(
195
+ current: Fiber,
196
+ workInProgress: Fiber,
197
+ type: Type,
198
+ newProps: Props,
199
+ rootContainerInstance: Container,
200
+ ) {
201
+ // If we have an alternate, that means this is an update and we need to
202
+ // schedule a side-effect to do the updates.
203
+ const oldProps = current.memoizedProps;
204
+ if (oldProps === newProps) {
205
+ // In mutation mode, this is sufficient for a bailout because
206
+ // we won't touch this node even if children changed.
207
+ return;
208
+ }
209
+
210
+ // If we get updated because one of our children updated, we don't
211
+ // have newProps so we'll have to reuse them.
212
+ // TODO: Split the update API as separate for the props vs. children.
213
+ // Even better would be if children weren't special cased at all tho.
214
+ const instance: Instance = workInProgress.stateNode;
215
+ const currentHostContext = getHostContext();
216
+ // TODO: Experiencing an error where oldProps is null. Suggests a host
217
+ // component is hitting the resume path. Figure out why. Possibly
218
+ // related to `hidden`.
219
+ const updatePayload = prepareUpdate(
220
+ instance,
221
+ type,
222
+ oldProps,
223
+ newProps,
224
+ rootContainerInstance,
225
+ currentHostContext,
226
+ );
227
+ // TODO: Type this specific to this type of component.
228
+ workInProgress.updateQueue = (updatePayload: any);
229
+ // If the update payload indicates that there is a change or if there
230
+ // is a new ref we mark this as an update. All the work is done in commitWork.
231
+ if (updatePayload) {
232
+ markUpdate(workInProgress);
233
+ }
234
+ };
235
+ updateHostText = function(
236
+ current: Fiber,
237
+ workInProgress: Fiber,
238
+ oldText: string,
239
+ newText: string,
240
+ ) {
241
+ // If the text differs, mark it as an update. All the work in done in commitWork.
242
+ if (oldText !== newText) {
243
+ markUpdate(workInProgress);
244
+ }
245
+ };
246
+} else if (supportsPersistence) {
247
+ // Persistent host tree mode
248
+
249
+ appendAllChildren = function(
250
+ parent: Instance,
251
+ workInProgress: Fiber,
252
+ needsVisibilityToggle: boolean,
253
+ isHidden: boolean,
254
+ ) {
255
+ // We only have the top Fiber that was created but we need recurse down its
256
+ // children to find all the terminal nodes.
257
+ let node = workInProgress.child;
258
+ while (node !== null) {
259
+ // eslint-disable-next-line no-labels
260
+ branches: if (node.tag === HostComponent) {
261
+ let instance = node.stateNode;
262
+ if (needsVisibilityToggle && isHidden) {
263
+ // This child is inside a timed out tree. Hide it.
264
+ const props = node.memoizedProps;
265
+ const type = node.type;
266
+ instance = cloneHiddenInstance(instance, type, props, node);
267
+ }
268
+ appendInitialChild(parent, instance);
269
+ } else if (node.tag === HostText) {
270
+ let instance = node.stateNode;
271
+ if (needsVisibilityToggle && isHidden) {
272
+ // This child is inside a timed out tree. Hide it.
273
+ const text = node.memoizedProps;
274
+ instance = cloneHiddenTextInstance(instance, text, node);
275
+ }
276
+ appendInitialChild(parent, instance);
277
+ } else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
278
+ let instance = node.stateNode.instance;
279
+ if (needsVisibilityToggle && isHidden) {
280
+ // This child is inside a timed out tree. Hide it.
281
+ const props = node.memoizedProps;
282
+ const type = node.type;
283
+ instance = cloneHiddenInstance(instance, type, props, node);
284
+ }
285
+ appendInitialChild(parent, instance);
286
+ } else if (node.tag === HostPortal) {
287
+ // If we have a portal child, then we don't want to traverse
288
+ // down its children. Instead, we'll get insertions from each child in
289
+ // the portal directly.
290
+ } else if (node.tag === SuspenseComponent) {
291
+ if ((node.effectTag & Update) !== NoEffect) {
292
+ // Need to toggle the visibility of the primary children.
293
+ const newIsHidden = node.memoizedState !== null;
294
+ if (newIsHidden) {
295
+ const primaryChildParent = node.child;
296
+ if (primaryChildParent !== null) {
297
+ if (primaryChildParent.child !== null) {
298
+ primaryChildParent.child.return = primaryChildParent;
299
+ appendAllChildren(
300
+ parent,
301
+ primaryChildParent,
302
+ true,
303
+ newIsHidden,
304
+ );
305
+ }
306
+ const fallbackChildParent = primaryChildParent.sibling;
307
+ if (fallbackChildParent !== null) {
308
+ fallbackChildParent.return = node;
309
+ node = fallbackChildParent;
310
+ continue;
311
+ }
312
+ }
313
+ }
314
+ }
315
+ if (node.child !== null) {
316
+ // Continue traversing like normal
317
+ node.child.return = node;
318
+ node = node.child;
319
+ continue;
320
+ }
321
+ } else if (node.child !== null) {
322
+ node.child.return = node;
323
+ node = node.child;
324
+ continue;
325
+ }
326
+ // $FlowFixMe This is correct but Flow is confused by the labeled break.
327
+ node = (node: Fiber);
328
+ if (node === workInProgress) {
329
+ return;
330
+ }
331
+ while (node.sibling === null) {
332
+ if (node.return === null || node.return === workInProgress) {
333
+ return;
334
+ }
335
+ node = node.return;
336
+ }
337
+ node.sibling.return = node.return;
338
+ node = node.sibling;
339
+ }
340
+ };
341
+
342
+ // An unfortunate fork of appendAllChildren because we have two different parent types.
343
+ const appendAllChildrenToContainer = function(
344
+ containerChildSet: ChildSet,
345
+ workInProgress: Fiber,
346
+ needsVisibilityToggle: boolean,
347
+ isHidden: boolean,
348
+ ) {
349
+ // We only have the top Fiber that was created but we need recurse down its
350
+ // children to find all the terminal nodes.
351
+ let node = workInProgress.child;
352
+ while (node !== null) {
353
+ // eslint-disable-next-line no-labels
354
+ branches: if (node.tag === HostComponent) {
355
+ let instance = node.stateNode;
356
+ if (needsVisibilityToggle && isHidden) {
357
+ // This child is inside a timed out tree. Hide it.
358
+ const props = node.memoizedProps;
359
+ const type = node.type;
360
+ instance = cloneHiddenInstance(instance, type, props, node);
361
+ }
362
+ appendChildToContainerChildSet(containerChildSet, instance);
363
+ } else if (node.tag === HostText) {
364
+ let instance = node.stateNode;
365
+ if (needsVisibilityToggle && isHidden) {
366
+ // This child is inside a timed out tree. Hide it.
367
+ const text = node.memoizedProps;
368
+ instance = cloneHiddenTextInstance(instance, text, node);
369
+ }
370
+ appendChildToContainerChildSet(containerChildSet, instance);
371
+ } else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
372
+ let instance = node.stateNode.instance;
373
+ if (needsVisibilityToggle && isHidden) {
374
+ // This child is inside a timed out tree. Hide it.
375
+ const props = node.memoizedProps;
376
+ const type = node.type;
377
+ instance = cloneHiddenInstance(instance, type, props, node);
378
+ }
379
+ appendChildToContainerChildSet(containerChildSet, instance);
380
+ } else if (node.tag === HostPortal) {
381
+ // If we have a portal child, then we don't want to traverse
382
+ // down its children. Instead, we'll get insertions from each child in
383
+ // the portal directly.
384
+ } else if (node.tag === SuspenseComponent) {
385
+ if ((node.effectTag & Update) !== NoEffect) {
386
+ // Need to toggle the visibility of the primary children.
387
+ const newIsHidden = node.memoizedState !== null;
388
+ if (newIsHidden) {
389
+ const primaryChildParent = node.child;
390
+ if (primaryChildParent !== null) {
391
+ if (primaryChildParent.child !== null) {
392
+ primaryChildParent.child.return = primaryChildParent;
393
+ appendAllChildrenToContainer(
394
+ containerChildSet,
395
+ primaryChildParent,
396
+ true,
397
+ newIsHidden,
398
+ );
399
+ }
400
+ const fallbackChildParent = primaryChildParent.sibling;
401
+ if (fallbackChildParent !== null) {
402
+ fallbackChildParent.return = node;
403
+ node = fallbackChildParent;
404
+ continue;
405
+ }
406
+ }
407
+ }
408
+ }
409
+ if (node.child !== null) {
410
+ // Continue traversing like normal
411
+ node.child.return = node;
412
+ node = node.child;
413
+ continue;
414
+ }
415
+ } else if (node.child !== null) {
416
+ node.child.return = node;
417
+ node = node.child;
418
+ continue;
419
+ }
420
+ // $FlowFixMe This is correct but Flow is confused by the labeled break.
421
+ node = (node: Fiber);
422
+ if (node === workInProgress) {
423
+ return;
424
+ }
425
+ while (node.sibling === null) {
426
+ if (node.return === null || node.return === workInProgress) {
427
+ return;
428
+ }
429
+ node = node.return;
430
+ }
431
+ node.sibling.return = node.return;
432
+ node = node.sibling;
433
+ }
434
+ };
435
+ updateHostContainer = function(workInProgress: Fiber) {
436
+ const portalOrRoot: {
437
+ containerInfo: Container,
438
+ pendingChildren: ChildSet,
439
+ ...
440
+ } = workInProgress.stateNode;
441
+ const childrenUnchanged = workInProgress.firstEffect === null;
442
+ if (childrenUnchanged) {
443
+ // No changes, just reuse the existing instance.
444
+ } else {
445
+ const container = portalOrRoot.containerInfo;
446
+ const newChildSet = createContainerChildSet(container);
447
+ // If children might have changed, we have to add them all to the set.
448
+ appendAllChildrenToContainer(newChildSet, workInProgress, false, false);
449
+ portalOrRoot.pendingChildren = newChildSet;
450
+ // Schedule an update on the container to swap out the container.
451
+ markUpdate(workInProgress);
452
+ finalizeContainerChildren(container, newChildSet);
453
+ }
454
+ };
455
+ updateHostComponent = function(
456
+ current: Fiber,
457
+ workInProgress: Fiber,
458
+ type: Type,
459
+ newProps: Props,
460
+ rootContainerInstance: Container,
461
+ ) {
462
+ const currentInstance = current.stateNode;
463
+ const oldProps = current.memoizedProps;
464
+ // If there are no effects associated with this node, then none of our children had any updates.
465
+ // This guarantees that we can reuse all of them.
466
+ const childrenUnchanged = workInProgress.firstEffect === null;
467
+ if (childrenUnchanged && oldProps === newProps) {
468
+ // No changes, just reuse the existing instance.
469
+ // Note that this might release a previous clone.
470
+ workInProgress.stateNode = currentInstance;
471
+ return;
472
+ }
473
+ const recyclableInstance: Instance = workInProgress.stateNode;
474
+ const currentHostContext = getHostContext();
475
+ let updatePayload = null;
476
+ if (oldProps !== newProps) {
477
+ updatePayload = prepareUpdate(
478
+ recyclableInstance,
479
+ type,
480
+ oldProps,
481
+ newProps,
482
+ rootContainerInstance,
483
+ currentHostContext,
484
+ );
485
+ }
486
+ if (childrenUnchanged && updatePayload === null) {
487
+ // No changes, just reuse the existing instance.
488
+ // Note that this might release a previous clone.
489
+ workInProgress.stateNode = currentInstance;
490
+ return;
491
+ }
492
+ const newInstance = cloneInstance(
493
+ currentInstance,
494
+ updatePayload,
495
+ type,
496
+ oldProps,
497
+ newProps,
498
+ workInProgress,
499
+ childrenUnchanged,
500
+ recyclableInstance,
501
+ );
502
+ if (
503
+ finalizeInitialChildren(
504
+ newInstance,
505
+ type,
506
+ newProps,
507
+ rootContainerInstance,
508
+ currentHostContext,
509
+ )
510
+ ) {
511
+ markUpdate(workInProgress);
512
+ }
513
+ workInProgress.stateNode = newInstance;
514
+ if (childrenUnchanged) {
515
+ // If there are no other effects in this tree, we need to flag this node as having one.
516
+ // Even though we're not going to use it for anything.
517
+ // Otherwise parents won't know that there are new children to propagate upwards.
518
+ markUpdate(workInProgress);
519
+ } else {
520
+ // If children might have changed, we have to add them all to the set.
521
+ appendAllChildren(newInstance, workInProgress, false, false);
522
+ }
523
+ };
524
+ updateHostText = function(
525
+ current: Fiber,
526
+ workInProgress: Fiber,
527
+ oldText: string,
528
+ newText: string,
529
+ ) {
530
+ if (oldText !== newText) {
531
+ // If the text content differs, we'll create a new text instance for it.
532
+ const rootContainerInstance = getRootHostContainer();
533
+ const currentHostContext = getHostContext();
534
+ workInProgress.stateNode = createTextInstance(
535
+ newText,
536
+ rootContainerInstance,
537
+ currentHostContext,
538
+ workInProgress,
539
+ );
540
+ // We'll have to mark it as having an effect, even though we won't use the effect for anything.
541
+ // This lets the parents know that at least one of their children has changed.
542
+ markUpdate(workInProgress);
543
+ } else {
544
+ workInProgress.stateNode = current.stateNode;
545
+ }
546
+ };
547
+} else {
548
+ // No host operations
549
+ updateHostContainer = function(workInProgress: Fiber) {
550
+ // Noop
551
+ };
552
+ updateHostComponent = function(
553
+ current: Fiber,
554
+ workInProgress: Fiber,
555
+ type: Type,
556
+ newProps: Props,
557
+ rootContainerInstance: Container,
558
+ ) {
559
+ // Noop
560
+ };
561
+ updateHostText = function(
562
+ current: Fiber,
563
+ workInProgress: Fiber,
564
+ oldText: string,
565
+ newText: string,
566
+ ) {
567
+ // Noop
568
+ };
569
+}
570
+
571
+function cutOffTailIfNeeded(
572
+ renderState: SuspenseListRenderState,
573
+ hasRenderedATailFallback: boolean,
574
+) {
575
+ switch (renderState.tailMode) {
576
+ case 'hidden': {
577
+ // Any insertions at the end of the tail list after this point
578
+ // should be invisible. If there are already mounted boundaries
579
+ // anything before them are not considered for collapsing.
580
+ // Therefore we need to go through the whole tail to find if
581
+ // there are any.
582
+ let tailNode = renderState.tail;
583
+ let lastTailNode = null;
584
+ while (tailNode !== null) {
585
+ if (tailNode.alternate !== null) {
586
+ lastTailNode = tailNode;
587
+ }
588
+ tailNode = tailNode.sibling;
589
+ }
590
+ // Next we're simply going to delete all insertions after the
591
+ // last rendered item.
592
+ if (lastTailNode === null) {
593
+ // All remaining items in the tail are insertions.
594
+ renderState.tail = null;
595
+ } else {
596
+ // Detach the insertion after the last node that was already
597
+ // inserted.
598
+ lastTailNode.sibling = null;
599
+ }
600
+ break;
601
+ }
602
+ case 'collapsed': {
603
+ // Any insertions at the end of the tail list after this point
604
+ // should be invisible. If there are already mounted boundaries
605
+ // anything before them are not considered for collapsing.
606
+ // Therefore we need to go through the whole tail to find if
607
+ // there are any.
608
+ let tailNode = renderState.tail;
609
+ let lastTailNode = null;
610
+ while (tailNode !== null) {
611
+ if (tailNode.alternate !== null) {
612
+ lastTailNode = tailNode;
613
+ }
614
+ tailNode = tailNode.sibling;
615
+ }
616
+ // Next we're simply going to delete all insertions after the
617
+ // last rendered item.
618
+ if (lastTailNode === null) {
619
+ // All remaining items in the tail are insertions.
620
+ if (!hasRenderedATailFallback && renderState.tail !== null) {
621
+ // We suspended during the head. We want to show at least one
622
+ // row at the tail. So we'll keep on and cut off the rest.
623
+ renderState.tail.sibling = null;
624
+ } else {
625
+ renderState.tail = null;
626
+ }
627
+ } else {
628
+ // Detach the insertion after the last node that was already
629
+ // inserted.
630
+ lastTailNode.sibling = null;
631
+ }
632
+ break;
633
+ }
634
+ }
635
+}
636
+
637
+function completeWork(
638
+ current: Fiber | null,
639
+ workInProgress: Fiber,
640
+ renderExpirationTime: ExpirationTime,
641
+): Fiber | null {
642
+ const newProps = workInProgress.pendingProps;
643
+
644
+ switch (workInProgress.tag) {
645
+ case IndeterminateComponent:
646
+ case LazyComponent:
647
+ case SimpleMemoComponent:
648
+ case FunctionComponent:
649
+ case ForwardRef:
650
+ case Fragment:
651
+ case Mode:
652
+ case Profiler:
653
+ case ContextConsumer:
654
+ case MemoComponent:
655
+ return null;
656
+ case ClassComponent: {
657
+ const Component = workInProgress.type;
658
+ if (isLegacyContextProvider(Component)) {
659
+ popLegacyContext(workInProgress);
660
+ }
661
+ return null;
662
+ }
663
+ case HostRoot: {
664
+ popHostContainer(workInProgress);
665
+ popTopLevelLegacyContextObject(workInProgress);
666
+ resetMutableSourceWorkInProgressVersions();
667
+ const fiberRoot = (workInProgress.stateNode: FiberRoot);
668
+ if (fiberRoot.pendingContext) {
669
+ fiberRoot.context = fiberRoot.pendingContext;
670
+ fiberRoot.pendingContext = null;
671
+ }
672
+ if (current === null || current.child === null) {
673
+ // If we hydrated, pop so that we can delete any remaining children
674
+ // that weren't hydrated.
675
+ const wasHydrated = popHydrationState(workInProgress);
676
+ if (wasHydrated) {
677
+ // If we hydrated, then we'll need to schedule an update for
678
+ // the commit side-effects on the root.
679
+ markUpdate(workInProgress);
680
+ }
681
+ }
682
+ updateHostContainer(workInProgress);
683
+ return null;
684
+ }
685
+ case HostComponent: {
686
+ popHostContext(workInProgress);
687
+ const rootContainerInstance = getRootHostContainer();
688
+ const type = workInProgress.type;
689
+ if (current !== null && workInProgress.stateNode != null) {
690
+ updateHostComponent(
691
+ current,
692
+ workInProgress,
693
+ type,
694
+ newProps,
695
+ rootContainerInstance,
696
+ );
697
+
698
+ if (enableDeprecatedFlareAPI) {
699
+ const prevListeners = current.memoizedProps.DEPRECATED_flareListeners;
700
+ const nextListeners = newProps.DEPRECATED_flareListeners;
701
+ if (prevListeners !== nextListeners) {
702
+ markUpdate(workInProgress);
703
+ }
704
+ }
705
+
706
+ if (current.ref !== workInProgress.ref) {
707
+ markRef(workInProgress);
708
+ }
709
+ } else {
710
+ if (!newProps) {
711
+ invariant(
712
+ workInProgress.stateNode !== null,
713
+ 'We must have new props for new mounts. This error is likely ' +
714
+ 'caused by a bug in React. Please file an issue.',
715
+ );
716
+ // This can happen when we abort work.
717
+ return null;
718
+ }
719
+
720
+ const currentHostContext = getHostContext();
721
+ // TODO: Move createInstance to beginWork and keep it on a context
722
+ // "stack" as the parent. Then append children as we go in beginWork
723
+ // or completeWork depending on whether we want to add them top->down or
724
+ // bottom->up. Top->down is faster in IE11.
725
+ const wasHydrated = popHydrationState(workInProgress);
726
+ if (wasHydrated) {
727
+ // TODO: Move this and createInstance step into the beginPhase
728
+ // to consolidate.
729
+ if (
730
+ prepareToHydrateHostInstance(
731
+ workInProgress,
732
+ rootContainerInstance,
733
+ currentHostContext,
734
+ )
735
+ ) {
736
+ // If changes to the hydrated node need to be applied at the
737
+ // commit-phase we mark this as such.
738
+ markUpdate(workInProgress);
739
+ }
740
+ if (enableDeprecatedFlareAPI) {
741
+ const listeners = newProps.DEPRECATED_flareListeners;
742
+ if (listeners != null) {
743
+ updateDeprecatedEventListeners(
744
+ listeners,
745
+ workInProgress,
746
+ rootContainerInstance,
747
+ );
748
+ }
749
+ }
750
+ } else {
751
+ const instance = createInstance(
752
+ type,
753
+ newProps,
754
+ rootContainerInstance,
755
+ currentHostContext,
756
+ workInProgress,
757
+ );
758
+
759
+ appendAllChildren(instance, workInProgress, false, false);
760
+
761
+ // This needs to be set before we mount Flare event listeners
762
+ workInProgress.stateNode = instance;
763
+
764
+ if (enableDeprecatedFlareAPI) {
765
+ const listeners = newProps.DEPRECATED_flareListeners;
766
+ if (listeners != null) {
767
+ updateDeprecatedEventListeners(
768
+ listeners,
769
+ workInProgress,
770
+ rootContainerInstance,
771
+ );
772
+ }
773
+ }
774
+
775
+ // Certain renderers require commit-time effects for initial mount.
776
+ // (eg DOM renderer supports auto-focus for certain elements).
777
+ // Make sure such renderers get scheduled for later work.
778
+ if (
779
+ finalizeInitialChildren(
780
+ instance,
781
+ type,
782
+ newProps,
783
+ rootContainerInstance,
784
+ currentHostContext,
785
+ )
786
+ ) {
787
+ markUpdate(workInProgress);
788
+ }
789
+ }
790
+
791
+ if (workInProgress.ref !== null) {
792
+ // If there is a ref on a host node we need to schedule a callback
793
+ markRef(workInProgress);
794
+ }
795
+ }
796
+ return null;
797
+ }
798
+ case HostText: {
799
+ const newText = newProps;
800
+ if (current && workInProgress.stateNode != null) {
801
+ const oldText = current.memoizedProps;
802
+ // If we have an alternate, that means this is an update and we need
803
+ // to schedule a side-effect to do the updates.
804
+ updateHostText(current, workInProgress, oldText, newText);
805
+ } else {
806
+ if (typeof newText !== 'string') {
807
+ invariant(
808
+ workInProgress.stateNode !== null,
809
+ 'We must have new props for new mounts. This error is likely ' +
810
+ 'caused by a bug in React. Please file an issue.',
811
+ );
812
+ // This can happen when we abort work.
813
+ }
814
+ const rootContainerInstance = getRootHostContainer();
815
+ const currentHostContext = getHostContext();
816
+ const wasHydrated = popHydrationState(workInProgress);
817
+ if (wasHydrated) {
818
+ if (prepareToHydrateHostTextInstance(workInProgress)) {
819
+ markUpdate(workInProgress);
820
+ }
821
+ } else {
822
+ workInProgress.stateNode = createTextInstance(
823
+ newText,
824
+ rootContainerInstance,
825
+ currentHostContext,
826
+ workInProgress,
827
+ );
828
+ }
829
+ }
830
+ return null;
831
+ }
832
+ case SuspenseComponent: {
833
+ popSuspenseContext(workInProgress);
834
+ const nextState: null | SuspenseState = workInProgress.memoizedState;
835
+
836
+ if (enableSuspenseServerRenderer) {
837
+ if (nextState !== null && nextState.dehydrated !== null) {
838
+ if (current === null) {
839
+ const wasHydrated = popHydrationState(workInProgress);
840
+ invariant(
841
+ wasHydrated,
842
+ 'A dehydrated suspense component was completed without a hydrated node. ' +
843
+ 'This is probably a bug in React.',
844
+ );
845
+ prepareToHydrateHostSuspenseInstance(workInProgress);
846
+ if (enableSchedulerTracing) {
847
+ markSpawnedWork(Never);
848
+ }
849
+ return null;
850
+ } else {
851
+ // We should never have been in a hydration state if we didn't have a current.
852
+ // However, in some of those paths, we might have reentered a hydration state
853
+ // and then we might be inside a hydration state. In that case, we'll need to exit out of it.
854
+ resetHydrationState();
855
+ if ((workInProgress.effectTag & DidCapture) === NoEffect) {
856
+ // This boundary did not suspend so it's now hydrated and unsuspended.
857
+ workInProgress.memoizedState = null;
858
+ }
859
+ // If nothing suspended, we need to schedule an effect to mark this boundary
860
+ // as having hydrated so events know that they're free to be invoked.
861
+ // It's also a signal to replay events and the suspense callback.
862
+ // If something suspended, schedule an effect to attach retry listeners.
863
+ // So we might as well always mark this.
864
+ workInProgress.effectTag |= Update;
865
+ return null;
866
+ }
867
+ }
868
+ }
869
+
870
+ if ((workInProgress.effectTag & DidCapture) !== NoEffect) {
871
+ // Something suspended. Re-render with the fallback children.
872
+ workInProgress.expirationTime = renderExpirationTime;
873
+ // Do not reset the effect list.
874
+ return workInProgress;
875
+ }
876
+
877
+ const nextDidTimeout = nextState !== null;
878
+ let prevDidTimeout = false;
879
+ if (current === null) {
880
+ if (workInProgress.memoizedProps.fallback !== undefined) {
881
+ popHydrationState(workInProgress);
882
+ }
883
+ } else {
884
+ const prevState: null | SuspenseState = current.memoizedState;
885
+ prevDidTimeout = prevState !== null;
886
+ if (!nextDidTimeout && prevState !== null) {
887
+ // We just switched from the fallback to the normal children.
888
+ // Delete the fallback.
889
+ // TODO: Would it be better to store the fallback fragment on
890
+ // the stateNode during the begin phase?
891
+ const currentFallbackChild: Fiber | null = (current.child: any)
892
+ .sibling;
893
+ if (currentFallbackChild !== null) {
894
+ // Deletions go at the beginning of the return fiber's effect list
895
+ const first = workInProgress.firstEffect;
896
+ if (first !== null) {
897
+ workInProgress.firstEffect = currentFallbackChild;
898
+ currentFallbackChild.nextEffect = first;
899
+ } else {
900
+ workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChild;
901
+ currentFallbackChild.nextEffect = null;
902
+ }
903
+ currentFallbackChild.effectTag = Deletion;
904
+ }
905
+ }
906
+ }
907
+
908
+ if (nextDidTimeout && !prevDidTimeout) {
909
+ // If this subtreee is running in blocking mode we can suspend,
910
+ // otherwise we won't suspend.
911
+ // TODO: This will still suspend a synchronous tree if anything
912
+ // in the concurrent tree already suspended during this render.
913
+ // This is a known bug.
914
+ if ((workInProgress.mode & BlockingMode) !== NoMode) {
915
+ // TODO: Move this back to throwException because this is too late
916
+ // if this is a large tree which is common for initial loads. We
917
+ // don't know if we should restart a render or not until we get
918
+ // this marker, and this is too late.
919
+ // If this render already had a ping or lower pri updates,
920
+ // and this is the first time we know we're going to suspend we
921
+ // should be able to immediately restart from within throwException.
922
+ const hasInvisibleChildContext =
923
+ current === null &&
924
+ workInProgress.memoizedProps.unstable_avoidThisFallback !== true;
925
+ if (
926
+ hasInvisibleChildContext ||
927
+ hasSuspenseContext(
928
+ suspenseStackCursor.current,
929
+ (InvisibleParentSuspenseContext: SuspenseContext),
930
+ )
931
+ ) {
932
+ // If this was in an invisible tree or a new render, then showing
933
+ // this boundary is ok.
934
+ renderDidSuspend();
935
+ } else {
936
+ // Otherwise, we're going to have to hide content so we should
937
+ // suspend for longer if possible.
938
+ renderDidSuspendDelayIfPossible();
939
+ }
940
+ }
941
+ }
942
+
943
+ if (supportsPersistence) {
944
+ // TODO: Only schedule updates if not prevDidTimeout.
945
+ if (nextDidTimeout) {
946
+ // If this boundary just timed out, schedule an effect to attach a
947
+ // retry listener to the promise. This flag is also used to hide the
948
+ // primary children.
949
+ workInProgress.effectTag |= Update;
950
+ }
951
+ }
952
+ if (supportsMutation) {
953
+ // TODO: Only schedule updates if these values are non equal, i.e. it changed.
954
+ if (nextDidTimeout || prevDidTimeout) {
955
+ // If this boundary just timed out, schedule an effect to attach a
956
+ // retry listener to the promise. This flag is also used to hide the
957
+ // primary children. In mutation mode, we also need the flag to
958
+ // *unhide* children that were previously hidden, so check if this
959
+ // is currently timed out, too.
960
+ workInProgress.effectTag |= Update;
961
+ }
962
+ }
963
+ if (
964
+ enableSuspenseCallback &&
965
+ workInProgress.updateQueue !== null &&
966
+ workInProgress.memoizedProps.suspenseCallback != null
967
+ ) {
968
+ // Always notify the callback
969
+ workInProgress.effectTag |= Update;
970
+ }
971
+ return null;
972
+ }
973
+ case HostPortal:
974
+ popHostContainer(workInProgress);
975
+ updateHostContainer(workInProgress);
976
+ return null;
977
+ case ContextProvider:
978
+ // Pop provider fiber
979
+ popProvider(workInProgress);
980
+ return null;
981
+ case IncompleteClassComponent: {
982
+ // Same as class component case. I put it down here so that the tags are
983
+ // sequential to ensure this switch is compiled to a jump table.
984
+ const Component = workInProgress.type;
985
+ if (isLegacyContextProvider(Component)) {
986
+ popLegacyContext(workInProgress);
987
+ }
988
+ return null;
989
+ }
990
+ case SuspenseListComponent: {
991
+ popSuspenseContext(workInProgress);
992
+
993
+ const renderState: null | SuspenseListRenderState =
994
+ workInProgress.memoizedState;
995
+
996
+ if (renderState === null) {
997
+ // We're running in the default, "independent" mode.
998
+ // We don't do anything in this mode.
999
+ return null;
1000
+ }
1001
+
1002
+ let didSuspendAlready =
1003
+ (workInProgress.effectTag & DidCapture) !== NoEffect;
1004
+
1005
+ const renderedTail = renderState.rendering;
1006
+ if (renderedTail === null) {
1007
+ // We just rendered the head.
1008
+ if (!didSuspendAlready) {
1009
+ // This is the first pass. We need to figure out if anything is still
1010
+ // suspended in the rendered set.
1011
+
1012
+ // If new content unsuspended, but there's still some content that
1013
+ // didn't. Then we need to do a second pass that forces everything
1014
+ // to keep showing their fallbacks.
1015
+
1016
+ // We might be suspended if something in this render pass suspended, or
1017
+ // something in the previous committed pass suspended. Otherwise,
1018
+ // there's no chance so we can skip the expensive call to
1019
+ // findFirstSuspended.
1020
+ const cannotBeSuspended =
1021
+ renderHasNotSuspendedYet() &&
1022
+ (current === null || (current.effectTag & DidCapture) === NoEffect);
1023
+ if (!cannotBeSuspended) {
1024
+ let row = workInProgress.child;
1025
+ while (row !== null) {
1026
+ const suspended = findFirstSuspended(row);
1027
+ if (suspended !== null) {
1028
+ didSuspendAlready = true;
1029
+ workInProgress.effectTag |= DidCapture;
1030
+ cutOffTailIfNeeded(renderState, false);
1031
+
1032
+ // If this is a newly suspended tree, it might not get committed as
1033
+ // part of the second pass. In that case nothing will subscribe to
1034
+ // its thennables. Instead, we'll transfer its thennables to the
1035
+ // SuspenseList so that it can retry if they resolve.
1036
+ // There might be multiple of these in the list but since we're
1037
+ // going to wait for all of them anyway, it doesn't really matter
1038
+ // which ones gets to ping. In theory we could get clever and keep
1039
+ // track of how many dependencies remain but it gets tricky because
1040
+ // in the meantime, we can add/remove/change items and dependencies.
1041
+ // We might bail out of the loop before finding any but that
1042
+ // doesn't matter since that means that the other boundaries that
1043
+ // we did find already has their listeners attached.
1044
+ const newThennables = suspended.updateQueue;
1045
+ if (newThennables !== null) {
1046
+ workInProgress.updateQueue = newThennables;
1047
+ workInProgress.effectTag |= Update;
1048
+ }
1049
+
1050
+ // Rerender the whole list, but this time, we'll force fallbacks
1051
+ // to stay in place.
1052
+ // Reset the effect list before doing the second pass since that's now invalid.
1053
+ if (renderState.lastEffect === null) {
1054
+ workInProgress.firstEffect = null;
1055
+ }
1056
+ workInProgress.lastEffect = renderState.lastEffect;
1057
+ // Reset the child fibers to their original state.
1058
+ resetChildFibers(workInProgress, renderExpirationTime);
1059
+
1060
+ // Set up the Suspense Context to force suspense and immediately
1061
+ // rerender the children.
1062
+ pushSuspenseContext(
1063
+ workInProgress,
1064
+ setShallowSuspenseContext(
1065
+ suspenseStackCursor.current,
1066
+ ForceSuspenseFallback,
1067
+ ),
1068
+ );
1069
+ return workInProgress.child;
1070
+ }
1071
+ row = row.sibling;
1072
+ }
1073
+ }
1074
+ } else {
1075
+ cutOffTailIfNeeded(renderState, false);
1076
+ }
1077
+ // Next we're going to render the tail.
1078
+ } else {
1079
+ // Append the rendered row to the child list.
1080
+ if (!didSuspendAlready) {
1081
+ const suspended = findFirstSuspended(renderedTail);
1082
+ if (suspended !== null) {
1083
+ workInProgress.effectTag |= DidCapture;
1084
+ didSuspendAlready = true;
1085
+
1086
+ // Ensure we transfer the update queue to the parent so that it doesn't
1087
+ // get lost if this row ends up dropped during a second pass.
1088
+ const newThennables = suspended.updateQueue;
1089
+ if (newThennables !== null) {
1090
+ workInProgress.updateQueue = newThennables;
1091
+ workInProgress.effectTag |= Update;
1092
+ }
1093
+
1094
+ cutOffTailIfNeeded(renderState, true);
1095
+ // This might have been modified.
1096
+ if (
1097
+ renderState.tail === null &&
1098
+ renderState.tailMode === 'hidden' &&
1099
+ !renderedTail.alternate
1100
+ ) {
1101
+ // We need to delete the row we just rendered.
1102
+ // Reset the effect list to what it was before we rendered this
1103
+ // child. The nested children have already appended themselves.
1104
+ const lastEffect = (workInProgress.lastEffect =
1105
+ renderState.lastEffect);
1106
+ // Remove any effects that were appended after this point.
1107
+ if (lastEffect !== null) {
1108
+ lastEffect.nextEffect = null;
1109
+ }
1110
+ // We're done.
1111
+ return null;
1112
+ }
1113
+ } else if (
1114
+ // The time it took to render last row is greater than time until
1115
+ // the expiration.
1116
+ now() * 2 - renderState.renderingStartTime >
1117
+ renderState.tailExpiration &&
1118
+ renderExpirationTime > Never
1119
+ ) {
1120
+ // We have now passed our CPU deadline and we'll just give up further
1121
+ // attempts to render the main content and only render fallbacks.
1122
+ // The assumption is that this is usually faster.
1123
+ workInProgress.effectTag |= DidCapture;
1124
+ didSuspendAlready = true;
1125
+
1126
+ cutOffTailIfNeeded(renderState, false);
1127
+
1128
+ // Since nothing actually suspended, there will nothing to ping this
1129
+ // to get it started back up to attempt the next item. If we can show
1130
+ // them, then they really have the same priority as this render.
1131
+ // So we'll pick it back up the very next render pass once we've had
1132
+ // an opportunity to yield for paint.
1133
+
1134
+ const nextPriority = renderExpirationTime - 1;
1135
+ workInProgress.expirationTime = workInProgress.childExpirationTime = nextPriority;
1136
+ if (enableSchedulerTracing) {
1137
+ markSpawnedWork(nextPriority);
1138
+ }
1139
+ }
1140
+ }
1141
+ if (renderState.isBackwards) {
1142
+ // The effect list of the backwards tail will have been added
1143
+ // to the end. This breaks the guarantee that life-cycles fire in
1144
+ // sibling order but that isn't a strong guarantee promised by React.
1145
+ // Especially since these might also just pop in during future commits.
1146
+ // Append to the beginning of the list.
1147
+ renderedTail.sibling = workInProgress.child;
1148
+ workInProgress.child = renderedTail;
1149
+ } else {
1150
+ const previousSibling = renderState.last;
1151
+ if (previousSibling !== null) {
1152
+ previousSibling.sibling = renderedTail;
1153
+ } else {
1154
+ workInProgress.child = renderedTail;
1155
+ }
1156
+ renderState.last = renderedTail;
1157
+ }
1158
+ }
1159
+
1160
+ if (renderState.tail !== null) {
1161
+ // We still have tail rows to render.
1162
+ if (renderState.tailExpiration === 0) {
1163
+ // Heuristic for how long we're willing to spend rendering rows
1164
+ // until we just give up and show what we have so far.
1165
+ const TAIL_EXPIRATION_TIMEOUT_MS = 500;
1166
+ renderState.tailExpiration = now() + TAIL_EXPIRATION_TIMEOUT_MS;
1167
+ // TODO: This is meant to mimic the train model or JND but this
1168
+ // is a per component value. It should really be since the start
1169
+ // of the total render or last commit. Consider using something like
1170
+ // globalMostRecentFallbackTime. That doesn't account for being
1171
+ // suspended for part of the time or when it's a new render.
1172
+ // It should probably use a global start time value instead.
1173
+ }
1174
+ // Pop a row.
1175
+ const next = renderState.tail;
1176
+ renderState.rendering = next;
1177
+ renderState.tail = next.sibling;
1178
+ renderState.lastEffect = workInProgress.lastEffect;
1179
+ renderState.renderingStartTime = now();
1180
+ next.sibling = null;
1181
+
1182
+ // Restore the context.
1183
+ // TODO: We can probably just avoid popping it instead and only
1184
+ // setting it the first time we go from not suspended to suspended.
1185
+ let suspenseContext = suspenseStackCursor.current;
1186
+ if (didSuspendAlready) {
1187
+ suspenseContext = setShallowSuspenseContext(
1188
+ suspenseContext,
1189
+ ForceSuspenseFallback,
1190
+ );
1191
+ } else {
1192
+ suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
1193
+ }
1194
+ pushSuspenseContext(workInProgress, suspenseContext);
1195
+ // Do a pass over the next row.
1196
+ return next;
1197
+ }
1198
+ return null;
1199
+ }
1200
+ case FundamentalComponent: {
1201
+ if (enableFundamentalAPI) {
1202
+ const fundamentalImpl = workInProgress.type.impl;
1203
+ let fundamentalInstance: ReactFundamentalComponentInstance<
1204
+ any,
1205
+ any,
1206
+ > | null = workInProgress.stateNode;
1207
+
1208
+ if (fundamentalInstance === null) {
1209
+ const getInitialState = fundamentalImpl.getInitialState;
1210
+ let fundamentalState;
1211
+ if (getInitialState !== undefined) {
1212
+ fundamentalState = getInitialState(newProps);
1213
+ }
1214
+ fundamentalInstance = workInProgress.stateNode = createFundamentalStateInstance(
1215
+ workInProgress,
1216
+ newProps,
1217
+ fundamentalImpl,
1218
+ fundamentalState || {},
1219
+ );
1220
+ const instance = ((getFundamentalComponentInstance(
1221
+ fundamentalInstance,
1222
+ ): any): Instance);
1223
+ fundamentalInstance.instance = instance;
1224
+ if (fundamentalImpl.reconcileChildren === false) {
1225
+ return null;
1226
+ }
1227
+ appendAllChildren(instance, workInProgress, false, false);
1228
+ mountFundamentalComponent(fundamentalInstance);
1229
+ } else {
1230
+ // We fire update in commit phase
1231
+ const prevProps = fundamentalInstance.props;
1232
+ fundamentalInstance.prevProps = prevProps;
1233
+ fundamentalInstance.props = newProps;
1234
+ fundamentalInstance.currentFiber = workInProgress;
1235
+ if (supportsPersistence) {
1236
+ const instance = cloneFundamentalInstance(fundamentalInstance);
1237
+ fundamentalInstance.instance = instance;
1238
+ appendAllChildren(instance, workInProgress, false, false);
1239
+ }
1240
+ const shouldUpdate = shouldUpdateFundamentalComponent(
1241
+ fundamentalInstance,
1242
+ );
1243
+ if (shouldUpdate) {
1244
+ markUpdate(workInProgress);
1245
+ }
1246
+ }
1247
+ return null;
1248
+ }
1249
+ break;
1250
+ }
1251
+ case ScopeComponent: {
1252
+ if (enableScopeAPI) {
1253
+ if (current === null) {
1254
+ const type = workInProgress.type;
1255
+ const scopeInstance: ReactScopeInstance = {
1256
+ fiber: workInProgress,
1257
+ methods: null,
1258
+ };
1259
+ workInProgress.stateNode = scopeInstance;
1260
+ scopeInstance.methods = createScopeMethods(type, scopeInstance);
1261
+ if (enableDeprecatedFlareAPI) {
1262
+ const listeners = newProps.DEPRECATED_flareListeners;
1263
+ if (listeners != null) {
1264
+ const rootContainerInstance = getRootHostContainer();
1265
+ updateDeprecatedEventListeners(
1266
+ listeners,
1267
+ workInProgress,
1268
+ rootContainerInstance,
1269
+ );
1270
+ }
1271
+ }
1272
+ if (workInProgress.ref !== null) {
1273
+ markRef(workInProgress);
1274
+ markUpdate(workInProgress);
1275
+ }
1276
+ } else {
1277
+ if (enableDeprecatedFlareAPI) {
1278
+ const prevListeners =
1279
+ current.memoizedProps.DEPRECATED_flareListeners;
1280
+ const nextListeners = newProps.DEPRECATED_flareListeners;
1281
+ if (
1282
+ prevListeners !== nextListeners ||
1283
+ workInProgress.ref !== null
1284
+ ) {
1285
+ markUpdate(workInProgress);
1286
+ }
1287
+ } else {
1288
+ if (workInProgress.ref !== null) {
1289
+ markUpdate(workInProgress);
1290
+ }
1291
+ }
1292
+ if (current.ref !== workInProgress.ref) {
1293
+ markRef(workInProgress);
1294
+ }
1295
+ }
1296
+ return null;
1297
+ }
1298
+ break;
1299
+ }
1300
+ case Block:
1301
+ if (enableBlocksAPI) {
1302
+ return null;
1303
+ }
1304
+ break;
1305
+ }
1306
+ invariant(
1307
+ false,
1308
+ 'Unknown unit of work tag (%s). This error is likely caused by a bug in ' +
1309
+ 'React. Please file an issue.',
1310
+ workInProgress.tag,
1311
+ );
1312
+}
1313
+
1314
+export {completeWork};
packages/react-reconciler/src/ReactFiberContext.new.js
new
+338
@@ -0,0 +1,338 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactInternalTypes';
11
+import type {StackCursor} from './ReactFiberStack.new';
12
+
13
+import {isFiberMounted} from './ReactFiberTreeReflection';
14
+import {disableLegacyContext} from 'shared/ReactFeatureFlags';
15
+import {ClassComponent, HostRoot} from './ReactWorkTags';
16
+import getComponentName from 'shared/getComponentName';
17
+import invariant from 'shared/invariant';
18
+import checkPropTypes from 'shared/checkPropTypes';
19
+
20
+import {createCursor, push, pop} from './ReactFiberStack.new';
21
+
22
+let warnedAboutMissingGetChildContext;
23
+
24
+if (__DEV__) {
25
+ warnedAboutMissingGetChildContext = {};
26
+}
27
+
28
+export const emptyContextObject = {};
29
+if (__DEV__) {
30
+ Object.freeze(emptyContextObject);
31
+}
32
+
33
+// A cursor to the current merged context object on the stack.
34
+const contextStackCursor: StackCursor<Object> = createCursor(
35
+ emptyContextObject,
36
+);
37
+// A cursor to a boolean indicating whether the context has changed.
38
+const didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
39
+// Keep track of the previous context object that was on the stack.
40
+// We use this to get access to the parent context after we have already
41
+// pushed the next context provider, and now need to merge their contexts.
42
+let previousContext: Object = emptyContextObject;
43
+
44
+function getUnmaskedContext(
45
+ workInProgress: Fiber,
46
+ Component: Function,
47
+ didPushOwnContextIfProvider: boolean,
48
+): Object {
49
+ if (disableLegacyContext) {
50
+ return emptyContextObject;
51
+ } else {
52
+ if (didPushOwnContextIfProvider && isContextProvider(Component)) {
53
+ // If the fiber is a context provider itself, when we read its context
54
+ // we may have already pushed its own child context on the stack. A context
55
+ // provider should not "see" its own child context. Therefore we read the
56
+ // previous (parent) context instead for a context provider.
57
+ return previousContext;
58
+ }
59
+ return contextStackCursor.current;
60
+ }
61
+}
62
+
63
+function cacheContext(
64
+ workInProgress: Fiber,
65
+ unmaskedContext: Object,
66
+ maskedContext: Object,
67
+): void {
68
+ if (disableLegacyContext) {
69
+ return;
70
+ } else {
71
+ const instance = workInProgress.stateNode;
72
+ instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
73
+ instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
74
+ }
75
+}
76
+
77
+function getMaskedContext(
78
+ workInProgress: Fiber,
79
+ unmaskedContext: Object,
80
+): Object {
81
+ if (disableLegacyContext) {
82
+ return emptyContextObject;
83
+ } else {
84
+ const type = workInProgress.type;
85
+ const contextTypes = type.contextTypes;
86
+ if (!contextTypes) {
87
+ return emptyContextObject;
88
+ }
89
+
90
+ // Avoid recreating masked context unless unmasked context has changed.
91
+ // Failing to do this will result in unnecessary calls to componentWillReceiveProps.
92
+ // This may trigger infinite loops if componentWillReceiveProps calls setState.
93
+ const instance = workInProgress.stateNode;
94
+ if (
95
+ instance &&
96
+ instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
97
+ ) {
98
+ return instance.__reactInternalMemoizedMaskedChildContext;
99
+ }
100
+
101
+ const context = {};
102
+ for (const key in contextTypes) {
103
+ context[key] = unmaskedContext[key];
104
+ }
105
+
106
+ if (__DEV__) {
107
+ const name = getComponentName(type) || 'Unknown';
108
+ checkPropTypes(contextTypes, context, 'context', name);
109
+ }
110
+
111
+ // Cache unmasked context so we can avoid recreating masked context unless necessary.
112
+ // Context is created before the class component is instantiated so check for instance.
113
+ if (instance) {
114
+ cacheContext(workInProgress, unmaskedContext, context);
115
+ }
116
+
117
+ return context;
118
+ }
119
+}
120
+
121
+function hasContextChanged(): boolean {
122
+ if (disableLegacyContext) {
123
+ return false;
124
+ } else {
125
+ return didPerformWorkStackCursor.current;
126
+ }
127
+}
128
+
129
+function isContextProvider(type: Function): boolean {
130
+ if (disableLegacyContext) {
131
+ return false;
132
+ } else {
133
+ const childContextTypes = type.childContextTypes;
134
+ return childContextTypes !== null && childContextTypes !== undefined;
135
+ }
136
+}
137
+
138
+function popContext(fiber: Fiber): void {
139
+ if (disableLegacyContext) {
140
+ return;
141
+ } else {
142
+ pop(didPerformWorkStackCursor, fiber);
143
+ pop(contextStackCursor, fiber);
144
+ }
145
+}
146
+
147
+function popTopLevelContextObject(fiber: Fiber): void {
148
+ if (disableLegacyContext) {
149
+ return;
150
+ } else {
151
+ pop(didPerformWorkStackCursor, fiber);
152
+ pop(contextStackCursor, fiber);
153
+ }
154
+}
155
+
156
+function pushTopLevelContextObject(
157
+ fiber: Fiber,
158
+ context: Object,
159
+ didChange: boolean,
160
+): void {
161
+ if (disableLegacyContext) {
162
+ return;
163
+ } else {
164
+ invariant(
165
+ contextStackCursor.current === emptyContextObject,
166
+ 'Unexpected context found on stack. ' +
167
+ 'This error is likely caused by a bug in React. Please file an issue.',
168
+ );
169
+
170
+ push(contextStackCursor, context, fiber);
171
+ push(didPerformWorkStackCursor, didChange, fiber);
172
+ }
173
+}
174
+
175
+function processChildContext(
176
+ fiber: Fiber,
177
+ type: any,
178
+ parentContext: Object,
179
+): Object {
180
+ if (disableLegacyContext) {
181
+ return parentContext;
182
+ } else {
183
+ const instance = fiber.stateNode;
184
+ const childContextTypes = type.childContextTypes;
185
+
186
+ // TODO (bvaughn) Replace this behavior with an invariant() in the future.
187
+ // It has only been added in Fiber to match the (unintentional) behavior in Stack.
188
+ if (typeof instance.getChildContext !== 'function') {
189
+ if (__DEV__) {
190
+ const componentName = getComponentName(type) || 'Unknown';
191
+
192
+ if (!warnedAboutMissingGetChildContext[componentName]) {
193
+ warnedAboutMissingGetChildContext[componentName] = true;
194
+ console.error(
195
+ '%s.childContextTypes is specified but there is no getChildContext() method ' +
196
+ 'on the instance. You can either define getChildContext() on %s or remove ' +
197
+ 'childContextTypes from it.',
198
+ componentName,
199
+ componentName,
200
+ );
201
+ }
202
+ }
203
+ return parentContext;
204
+ }
205
+
206
+ const childContext = instance.getChildContext();
207
+ for (const contextKey in childContext) {
208
+ invariant(
209
+ contextKey in childContextTypes,
210
+ '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
211
+ getComponentName(type) || 'Unknown',
212
+ contextKey,
213
+ );
214
+ }
215
+ if (__DEV__) {
216
+ const name = getComponentName(type) || 'Unknown';
217
+ checkPropTypes(childContextTypes, childContext, 'child context', name);
218
+ }
219
+
220
+ return {...parentContext, ...childContext};
221
+ }
222
+}
223
+
224
+function pushContextProvider(workInProgress: Fiber): boolean {
225
+ if (disableLegacyContext) {
226
+ return false;
227
+ } else {
228
+ const instance = workInProgress.stateNode;
229
+ // We push the context as early as possible to ensure stack integrity.
230
+ // If the instance does not exist yet, we will push null at first,
231
+ // and replace it on the stack later when invalidating the context.
232
+ const memoizedMergedChildContext =
233
+ (instance && instance.__reactInternalMemoizedMergedChildContext) ||
234
+ emptyContextObject;
235
+
236
+ // Remember the parent context so we can merge with it later.
237
+ // Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
238
+ previousContext = contextStackCursor.current;
239
+ push(contextStackCursor, memoizedMergedChildContext, workInProgress);
240
+ push(
241
+ didPerformWorkStackCursor,
242
+ didPerformWorkStackCursor.current,
243
+ workInProgress,
244
+ );
245
+
246
+ return true;
247
+ }
248
+}
249
+
250
+function invalidateContextProvider(
251
+ workInProgress: Fiber,
252
+ type: any,
253
+ didChange: boolean,
254
+): void {
255
+ if (disableLegacyContext) {
256
+ return;
257
+ } else {
258
+ const instance = workInProgress.stateNode;
259
+ invariant(
260
+ instance,
261
+ 'Expected to have an instance by this point. ' +
262
+ 'This error is likely caused by a bug in React. Please file an issue.',
263
+ );
264
+
265
+ if (didChange) {
266
+ // Merge parent and own context.
267
+ // Skip this if we're not updating due to sCU.
268
+ // This avoids unnecessarily recomputing memoized values.
269
+ const mergedContext = processChildContext(
270
+ workInProgress,
271
+ type,
272
+ previousContext,
273
+ );
274
+ instance.__reactInternalMemoizedMergedChildContext = mergedContext;
275
+
276
+ // Replace the old (or empty) context with the new one.
277
+ // It is important to unwind the context in the reverse order.
278
+ pop(didPerformWorkStackCursor, workInProgress);
279
+ pop(contextStackCursor, workInProgress);
280
+ // Now push the new context and mark that it has changed.
281
+ push(contextStackCursor, mergedContext, workInProgress);
282
+ push(didPerformWorkStackCursor, didChange, workInProgress);
283
+ } else {
284
+ pop(didPerformWorkStackCursor, workInProgress);
285
+ push(didPerformWorkStackCursor, didChange, workInProgress);
286
+ }
287
+ }
288
+}
289
+
290
+function findCurrentUnmaskedContext(fiber: Fiber): Object {
291
+ if (disableLegacyContext) {
292
+ return emptyContextObject;
293
+ } else {
294
+ // Currently this is only used with renderSubtreeIntoContainer; not sure if it
295
+ // makes sense elsewhere
296
+ invariant(
297
+ isFiberMounted(fiber) && fiber.tag === ClassComponent,
298
+ 'Expected subtree parent to be a mounted class component. ' +
299
+ 'This error is likely caused by a bug in React. Please file an issue.',
300
+ );
301
+
302
+ let node = fiber;
303
+ do {
304
+ switch (node.tag) {
305
+ case HostRoot:
306
+ return node.stateNode.context;
307
+ case ClassComponent: {
308
+ const Component = node.type;
309
+ if (isContextProvider(Component)) {
310
+ return node.stateNode.__reactInternalMemoizedMergedChildContext;
311
+ }
312
+ break;
313
+ }
314
+ }
315
+ node = node.return;
316
+ } while (node !== null);
317
+ invariant(
318
+ false,
319
+ 'Found unexpected detached subtree parent. ' +
320
+ 'This error is likely caused by a bug in React. Please file an issue.',
321
+ );
322
+ }
323
+}
324
+
325
+export {
326
+ getUnmaskedContext,
327
+ cacheContext,
328
+ getMaskedContext,
329
+ hasContextChanged,
330
+ popContext,
331
+ popTopLevelContextObject,
332
+ pushTopLevelContextObject,
333
+ processChildContext,
334
+ isContextProvider,
335
+ pushContextProvider,
336
+ invalidateContextProvider,
337
+ findCurrentUnmaskedContext,
338
+};
packages/react-reconciler/src/ReactFiberDeprecatedEvents.new.js
new
+234
@@ -0,0 +1,234 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactInternalTypes';
11
+import type {Container, Instance} from './ReactFiberHostConfig';
12
+import type {
13
+ ReactEventResponder,
14
+ ReactEventResponderInstance,
15
+ ReactEventResponderListener,
16
+} from 'shared/ReactTypes';
17
+
18
+import {
19
+ DEPRECATED_mountResponderInstance,
20
+ DEPRECATED_unmountResponderInstance,
21
+} from './ReactFiberHostConfig';
22
+import {NoWork} from './ReactFiberExpirationTime';
23
+
24
+import {REACT_RESPONDER_TYPE} from 'shared/ReactSymbols';
25
+
26
+import invariant from 'shared/invariant';
27
+import {HostComponent, HostRoot} from './ReactWorkTags';
28
+
29
+const emptyObject = {};
30
+const isArray = Array.isArray;
31
+
32
+export function createResponderInstance(
33
+ responder: ReactEventResponder<any, any>,
34
+ responderProps: Object,
35
+ responderState: Object,
36
+ fiber: Fiber,
37
+): ReactEventResponderInstance<any, any> {
38
+ return {
39
+ fiber,
40
+ props: responderProps,
41
+ responder,
42
+ rootEventTypes: null,
43
+ state: responderState,
44
+ };
45
+}
46
+
47
+function mountEventResponder(
48
+ responder: ReactEventResponder<any, any>,
49
+ responderProps: Object,
50
+ fiber: Fiber,
51
+ respondersMap: Map<
52
+ ReactEventResponder<any, any>,
53
+ ReactEventResponderInstance<any, any>,
54
+ >,
55
+ rootContainerInstance: null | Container,
56
+) {
57
+ let responderState = emptyObject;
58
+ const getInitialState = responder.getInitialState;
59
+ if (getInitialState !== null) {
60
+ responderState = getInitialState(responderProps);
61
+ }
62
+ const responderInstance = createResponderInstance(
63
+ responder,
64
+ responderProps,
65
+ responderState,
66
+ fiber,
67
+ );
68
+
69
+ if (!rootContainerInstance) {
70
+ let node = fiber;
71
+ while (node !== null) {
72
+ const tag = node.tag;
73
+ if (tag === HostComponent) {
74
+ rootContainerInstance = node.stateNode;
75
+ break;
76
+ } else if (tag === HostRoot) {
77
+ rootContainerInstance = node.stateNode.containerInfo;
78
+ break;
79
+ }
80
+ node = node.return;
81
+ }
82
+ }
83
+
84
+ DEPRECATED_mountResponderInstance(
85
+ responder,
86
+ responderInstance,
87
+ responderProps,
88
+ responderState,
89
+ ((rootContainerInstance: any): Instance),
90
+ );
91
+ respondersMap.set(responder, responderInstance);
92
+}
93
+
94
+function updateEventListener(
95
+ listener: ReactEventResponderListener<any, any>,
96
+ fiber: Fiber,
97
+ visistedResponders: Set<ReactEventResponder<any, any>>,
98
+ respondersMap: Map<
99
+ ReactEventResponder<any, any>,
100
+ ReactEventResponderInstance<any, any>,
101
+ >,
102
+ rootContainerInstance: null | Container,
103
+): void {
104
+ let responder;
105
+ let props;
106
+
107
+ if (listener) {
108
+ responder = listener.responder;
109
+ props = listener.props;
110
+ }
111
+ invariant(
112
+ responder && responder.$$typeof === REACT_RESPONDER_TYPE,
113
+ 'An invalid value was used as an event listener. Expect one or many event ' +
114
+ 'listeners created via React.unstable_useResponder().',
115
+ );
116
+ const listenerProps = ((props: any): Object);
117
+ if (visistedResponders.has(responder)) {
118
+ // show warning
119
+ if (__DEV__) {
120
+ console.error(
121
+ 'Duplicate event responder "%s" found in event listeners. ' +
122
+ 'Event listeners passed to elements cannot use the same event responder more than once.',
123
+ responder.displayName,
124
+ );
125
+ }
126
+ return;
127
+ }
128
+ visistedResponders.add(responder);
129
+ const responderInstance = respondersMap.get(responder);
130
+
131
+ if (responderInstance === undefined) {
132
+ // Mount (happens in either complete or commit phase)
133
+ mountEventResponder(
134
+ responder,
135
+ listenerProps,
136
+ fiber,
137
+ respondersMap,
138
+ rootContainerInstance,
139
+ );
140
+ } else {
141
+ // Update (happens during commit phase only)
142
+ responderInstance.props = listenerProps;
143
+ responderInstance.fiber = fiber;
144
+ }
145
+}
146
+
147
+export function updateDeprecatedEventListeners(
148
+ listeners: any,
149
+ fiber: Fiber,
150
+ rootContainerInstance: null | Container,
151
+): void {
152
+ const visistedResponders = new Set();
153
+ let dependencies = fiber.dependencies;
154
+ if (listeners != null) {
155
+ if (dependencies === null) {
156
+ dependencies = fiber.dependencies = {
157
+ expirationTime: NoWork,
158
+ firstContext: null,
159
+ responders: new Map(),
160
+ };
161
+ }
162
+ let respondersMap = dependencies.responders;
163
+ if (respondersMap === null) {
164
+ dependencies.responders = respondersMap = new Map();
165
+ }
166
+ if (isArray(listeners)) {
167
+ for (let i = 0, length = listeners.length; i < length; i++) {
168
+ const listener = listeners[i];
169
+ updateEventListener(
170
+ listener,
171
+ fiber,
172
+ visistedResponders,
173
+ respondersMap,
174
+ rootContainerInstance,
175
+ );
176
+ }
177
+ } else {
178
+ updateEventListener(
179
+ listeners,
180
+ fiber,
181
+ visistedResponders,
182
+ respondersMap,
183
+ rootContainerInstance,
184
+ );
185
+ }
186
+ }
187
+ if (dependencies !== null) {
188
+ const respondersMap = dependencies.responders;
189
+ if (respondersMap !== null) {
190
+ // Unmount
191
+ const mountedResponders = Array.from(respondersMap.keys());
192
+ for (let i = 0, length = mountedResponders.length; i < length; i++) {
193
+ const mountedResponder = mountedResponders[i];
194
+ if (!visistedResponders.has(mountedResponder)) {
195
+ const responderInstance = ((respondersMap.get(
196
+ mountedResponder,
197
+ ): any): ReactEventResponderInstance<any, any>);
198
+ DEPRECATED_unmountResponderInstance(responderInstance);
199
+ respondersMap.delete(mountedResponder);
200
+ }
201
+ }
202
+ }
203
+ }
204
+}
205
+
206
+export function createDeprecatedResponderListener(
207
+ responder: ReactEventResponder<any, any>,
208
+ props: Object,
209
+): ReactEventResponderListener<any, any> {
210
+ const eventResponderListener = {
211
+ responder,
212
+ props,
213
+ };
214
+ if (__DEV__) {
215
+ Object.freeze(eventResponderListener);
216
+ }
217
+ return eventResponderListener;
218
+}
219
+
220
+export function unmountDeprecatedResponderListeners(fiber: Fiber) {
221
+ const dependencies = fiber.dependencies;
222
+
223
+ if (dependencies !== null) {
224
+ const respondersMap = dependencies.responders;
225
+ if (respondersMap !== null) {
226
+ const responderInstances = Array.from(respondersMap.values());
227
+ for (let i = 0, length = responderInstances.length; i < length; i++) {
228
+ const responderInstance = responderInstances[i];
229
+ DEPRECATED_unmountResponderInstance(responderInstance);
230
+ }
231
+ dependencies.responders = null;
232
+ }
233
+ }
234
+}
packages/react-reconciler/src/ReactFiberDevToolsHook.new.js
new
+128
@@ -0,0 +1,128 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
11
+import {getCurrentTime} from './ReactFiberWorkLoop.new';
12
+import {inferPriorityFromExpirationTime} from './ReactFiberExpirationTime';
13
+
14
+import type {Fiber} from './ReactInternalTypes';
15
+import type {FiberRoot} from './ReactInternalTypes';
16
+import type {ExpirationTime} from './ReactFiberExpirationTime';
17
+import type {ReactNodeList} from 'shared/ReactTypes';
18
+
19
+import {DidCapture} from './ReactSideEffectTags';
20
+
21
+declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void;
22
+
23
+let rendererID = null;
24
+let injectedHook = null;
25
+let hasLoggedError = false;
26
+
27
+export const isDevToolsPresent =
28
+ typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined';
29
+
30
+export function injectInternals(internals: Object): boolean {
31
+ if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') {
32
+ // No DevTools
33
+ return false;
34
+ }
35
+ const hook = __REACT_DEVTOOLS_GLOBAL_HOOK__;
36
+ if (hook.isDisabled) {
37
+ // This isn't a real property on the hook, but it can be set to opt out
38
+ // of DevTools integration and associated warnings and logs.
39
+ // https://github.com/facebook/react/issues/3877
40
+ return true;
41
+ }
42
+ if (!hook.supportsFiber) {
43
+ if (__DEV__) {
44
+ console.error(
45
+ 'The installed version of React DevTools is too old and will not work ' +
46
+ 'with the current version of React. Please update React DevTools. ' +
47
+ 'https://fb.me/react-devtools',
48
+ );
49
+ }
50
+ // DevTools exists, even though it doesn't support Fiber.
51
+ return true;
52
+ }
53
+ try {
54
+ rendererID = hook.inject(internals);
55
+ // We have successfully injected, so now it is safe to set up hooks.
56
+ injectedHook = hook;
57
+ } catch (err) {
58
+ // Catch all errors because it is unsafe to throw during initialization.
59
+ if (__DEV__) {
60
+ console.error('React instrumentation encountered an error: %s.', err);
61
+ }
62
+ }
63
+ // DevTools exists
64
+ return true;
65
+}
66
+
67
+export function onScheduleRoot(root: FiberRoot, children: ReactNodeList) {
68
+ if (__DEV__) {
69
+ if (
70
+ injectedHook &&
71
+ typeof injectedHook.onScheduleFiberRoot === 'function'
72
+ ) {
73
+ try {
74
+ injectedHook.onScheduleFiberRoot(rendererID, root, children);
75
+ } catch (err) {
76
+ if (__DEV__ && !hasLoggedError) {
77
+ hasLoggedError = true;
78
+ console.error('React instrumentation encountered an error: %s', err);
79
+ }
80
+ }
81
+ }
82
+ }
83
+}
84
+
85
+export function onCommitRoot(root: FiberRoot, expirationTime: ExpirationTime) {
86
+ if (injectedHook && typeof injectedHook.onCommitFiberRoot === 'function') {
87
+ try {
88
+ const didError = (root.current.effectTag & DidCapture) === DidCapture;
89
+ if (enableProfilerTimer) {
90
+ const currentTime = getCurrentTime();
91
+ const priorityLevel = inferPriorityFromExpirationTime(
92
+ currentTime,
93
+ expirationTime,
94
+ );
95
+ injectedHook.onCommitFiberRoot(
96
+ rendererID,
97
+ root,
98
+ priorityLevel,
99
+ didError,
100
+ );
101
+ } else {
102
+ injectedHook.onCommitFiberRoot(rendererID, root, undefined, didError);
103
+ }
104
+ } catch (err) {
105
+ if (__DEV__) {
106
+ if (!hasLoggedError) {
107
+ hasLoggedError = true;
108
+ console.error('React instrumentation encountered an error: %s', err);
109
+ }
110
+ }
111
+ }
112
+ }
113
+}
114
+
115
+export function onCommitUnmount(fiber: Fiber) {
116
+ if (injectedHook && typeof injectedHook.onCommitFiberUnmount === 'function') {
117
+ try {
118
+ injectedHook.onCommitFiberUnmount(rendererID, fiber);
119
+ } catch (err) {
120
+ if (__DEV__) {
121
+ if (!hasLoggedError) {
122
+ hasLoggedError = true;
123
+ console.error('React instrumentation encountered an error: %s', err);
124
+ }
125
+ }
126
+ }
127
+ }
128
+}
packages/react-reconciler/src/ReactFiberFundamental.new.js
new
+30
@@ -0,0 +1,30 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactInternalTypes';
11
+import type {
12
+ ReactFundamentalImpl,
13
+ ReactFundamentalComponentInstance,
14
+} from 'shared/ReactTypes';
15
+
16
+export function createFundamentalStateInstance<C, H>(
17
+ currentFiber: Fiber,
18
+ props: Object,
19
+ impl: ReactFundamentalImpl<C, H>,
20
+ state: Object,
21
+): ReactFundamentalComponentInstance<C, H> {
22
+ return {
23
+ currentFiber,
24
+ impl,
25
+ instance: null,
26
+ prevProps: null,
27
+ props,
28
+ state,
29
+ };
30
+}
packages/react-reconciler/src/ReactFiberHooks.new.js
new
+3003
@@ -0,0 +1,3003 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {
11
+ MutableSource,
12
+ MutableSourceGetSnapshotFn,
13
+ MutableSourceSubscribeFn,
14
+ ReactEventResponder,
15
+ ReactContext,
16
+ ReactEventResponderListener,
17
+ ReactScopeMethods,
18
+} from 'shared/ReactTypes';
19
+import type {Fiber, Dispatcher} from './ReactInternalTypes';
20
+import type {ExpirationTime} from './ReactFiberExpirationTime';
21
+import type {HookEffectTag} from './ReactHookEffectTags';
22
+import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
23
+import type {ReactPriorityLevel} from './ReactInternalTypes';
24
+import type {FiberRoot} from './ReactInternalTypes';
25
+import type {
26
+ OpaqueIDType,
27
+ ReactListenerEvent,
28
+ ReactListenerMap,
29
+ ReactListener,
30
+} from './ReactFiberHostConfig';
31
+
32
+import ReactSharedInternals from 'shared/ReactSharedInternals';
33
+import {enableUseEventAPI} from 'shared/ReactFeatureFlags';
34
+
35
+import {markRootExpiredAtTime} from './ReactFiberRoot.new';
36
+import {NoWork, Sync} from './ReactFiberExpirationTime';
37
+import {NoMode, BlockingMode} from './ReactTypeOfMode';
38
+import {readContext} from './ReactFiberNewContext.new';
39
+import {createDeprecatedResponderListener} from './ReactFiberDeprecatedEvents.new';
40
+import {
41
+ Update as UpdateEffect,
42
+ Passive as PassiveEffect,
43
+} from './ReactSideEffectTags';
44
+import {
45
+ NoEffect as NoHookEffect,
46
+ HasEffect as HookHasEffect,
47
+ Layout as HookLayout,
48
+ Passive as HookPassive,
49
+} from './ReactHookEffectTags';
50
+import {
51
+ getWorkInProgressRoot,
52
+ scheduleUpdateOnFiber,
53
+ computeExpirationForFiber,
54
+ requestCurrentTimeForUpdate,
55
+ warnIfNotCurrentlyActingEffectsInDEV,
56
+ warnIfNotCurrentlyActingUpdatesInDev,
57
+ warnIfNotScopedWithMatchingAct,
58
+ markRenderEventTimeAndConfig,
59
+ markUnprocessedUpdateTime,
60
+} from './ReactFiberWorkLoop.new';
61
+import {
62
+ registerEvent,
63
+ mountEventListener as mountHostEventListener,
64
+ unmountEventListener as unmountHostEventListener,
65
+ validateEventListenerTarget,
66
+} from './ReactFiberHostConfig';
67
+
68
+import invariant from 'shared/invariant';
69
+import getComponentName from 'shared/getComponentName';
70
+import is from 'shared/objectIs';
71
+import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.new';
72
+import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
73
+import {
74
+ UserBlockingPriority,
75
+ NormalPriority,
76
+ runWithPriority,
77
+ getCurrentPriorityLevel,
78
+} from './SchedulerWithReactIntegration.new';
79
+import {getIsHydrating} from './ReactFiberHydrationContext.new';
80
+import {
81
+ makeClientId,
82
+ makeClientIdInDEV,
83
+ makeOpaqueHydratingObject,
84
+} from './ReactFiberHostConfig';
85
+import {
86
+ getLastPendingExpirationTime,
87
+ getWorkInProgressVersion,
88
+ markSourceAsDirty,
89
+ setPendingExpirationTime,
90
+ setWorkInProgressVersion,
91
+ warnAboutMultipleRenderersDEV,
92
+} from './ReactMutableSource.new';
93
+import {getRootHostContainer} from './ReactFiberHostContext.new';
94
+import {getIsRendering} from './ReactCurrentFiber';
95
+
96
+const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
97
+
98
+type Update<S, A> = {|
99
+ expirationTime: ExpirationTime,
100
+ suspenseConfig: null | SuspenseConfig,
101
+ action: A,
102
+ eagerReducer: ((S, A) => S) | null,
103
+ eagerState: S | null,
104
+ next: Update<S, A>,
105
+ priority?: ReactPriorityLevel,
106
+|};
107
+
108
+type UpdateQueue<S, A> = {|
109
+ pending: Update<S, A> | null,
110
+ dispatch: (A => mixed) | null,
111
+ lastRenderedReducer: ((S, A) => S) | null,
112
+ lastRenderedState: S | null,
113
+|};
114
+
115
+export type HookType =
116
+ | 'useState'
117
+ | 'useReducer'
118
+ | 'useContext'
119
+ | 'useRef'
120
+ | 'useEffect'
121
+ | 'useLayoutEffect'
122
+ | 'useCallback'
123
+ | 'useMemo'
124
+ | 'useImperativeHandle'
125
+ | 'useDebugValue'
126
+ | 'useResponder'
127
+ | 'useDeferredValue'
128
+ | 'useTransition'
129
+ | 'useMutableSource'
130
+ | 'useEvent'
131
+ | 'useOpaqueIdentifier';
132
+
133
+let didWarnAboutMismatchedHooksForComponent;
134
+let didWarnAboutUseOpaqueIdentifier;
135
+if (__DEV__) {
136
+ didWarnAboutUseOpaqueIdentifier = {};
137
+ didWarnAboutMismatchedHooksForComponent = new Set();
138
+}
139
+
140
+export type Hook = {|
141
+ memoizedState: any,
142
+ baseState: any,
143
+ baseQueue: Update<any, any> | null,
144
+ queue: UpdateQueue<any, any> | null,
145
+ next: Hook | null,
146
+|};
147
+
148
+export type Effect = {|
149
+ tag: HookEffectTag,
150
+ create: () => (() => void) | void,
151
+ destroy: (() => void) | void,
152
+ deps: Array<mixed> | null,
153
+ next: Effect,
154
+|};
155
+
156
+export type FunctionComponentUpdateQueue = {|lastEffect: Effect | null|};
157
+
158
+type TimeoutConfig = {|
159
+ timeoutMs: number,
160
+|};
161
+
162
+type BasicStateAction<S> = (S => S) | S;
163
+
164
+type Dispatch<A> = A => void;
165
+
166
+// These are set right before calling the component.
167
+let renderExpirationTime: ExpirationTime = NoWork;
168
+// The work-in-progress fiber. I've named it differently to distinguish it from
169
+// the work-in-progress hook.
170
+let currentlyRenderingFiber: Fiber = (null: any);
171
+
172
+// Hooks are stored as a linked list on the fiber's memoizedState field. The
173
+// current hook list is the list that belongs to the current fiber. The
174
+// work-in-progress hook list is a new list that will be added to the
175
+// work-in-progress fiber.
176
+let currentHook: Hook | null = null;
177
+let workInProgressHook: Hook | null = null;
178
+
179
+// Whether an update was scheduled at any point during the render phase. This
180
+// does not get reset if we do another render pass; only when we're completely
181
+// finished evaluating this component. This is an optimization so we know
182
+// whether we need to clear render phase updates after a throw.
183
+let didScheduleRenderPhaseUpdate: boolean = false;
184
+// Where an update was scheduled only during the current render pass. This
185
+// gets reset after each attempt.
186
+// TODO: Maybe there's some way to consolidate this with
187
+// `didScheduleRenderPhaseUpdate`. Or with `numberOfReRenders`.
188
+let didScheduleRenderPhaseUpdateDuringThisPass: boolean = false;
189
+
190
+const RE_RENDER_LIMIT = 25;
191
+
192
+// In DEV, this is the name of the currently executing primitive hook
193
+let currentHookNameInDev: ?HookType = null;
194
+
195
+// In DEV, this list ensures that hooks are called in the same order between renders.
196
+// The list stores the order of hooks used during the initial render (mount).
197
+// Subsequent renders (updates) reference this list.
198
+let hookTypesDev: Array<HookType> | null = null;
199
+let hookTypesUpdateIndexDev: number = -1;
200
+
201
+// In DEV, this tracks whether currently rendering component needs to ignore
202
+// the dependencies for Hooks that need them (e.g. useEffect or useMemo).
203
+// When true, such Hooks will always be "remounted". Only used during hot reload.
204
+let ignorePreviousDependencies: boolean = false;
205
+
206
+function mountHookTypesDev() {
207
+ if (__DEV__) {
208
+ const hookName = ((currentHookNameInDev: any): HookType);
209
+
210
+ if (hookTypesDev === null) {
211
+ hookTypesDev = [hookName];
212
+ } else {
213
+ hookTypesDev.push(hookName);
214
+ }
215
+ }
216
+}
217
+
218
+function updateHookTypesDev() {
219
+ if (__DEV__) {
220
+ const hookName = ((currentHookNameInDev: any): HookType);
221
+
222
+ if (hookTypesDev !== null) {
223
+ hookTypesUpdateIndexDev++;
224
+ if (hookTypesDev[hookTypesUpdateIndexDev] !== hookName) {
225
+ warnOnHookMismatchInDev(hookName);
226
+ }
227
+ }
228
+ }
229
+}
230
+
231
+function checkDepsAreArrayDev(deps: mixed) {
232
+ if (__DEV__) {
233
+ if (deps !== undefined && deps !== null && !Array.isArray(deps)) {
234
+ // Verify deps, but only on mount to avoid extra checks.
235
+ // It's unlikely their type would change as usually you define them inline.
236
+ console.error(
237
+ '%s received a final argument that is not an array (instead, received `%s`). When ' +
238
+ 'specified, the final argument must be an array.',
239
+ currentHookNameInDev,
240
+ typeof deps,
241
+ );
242
+ }
243
+ }
244
+}
245
+
246
+function warnOnHookMismatchInDev(currentHookName: HookType) {
247
+ if (__DEV__) {
248
+ const componentName = getComponentName(currentlyRenderingFiber.type);
249
+ if (!didWarnAboutMismatchedHooksForComponent.has(componentName)) {
250
+ didWarnAboutMismatchedHooksForComponent.add(componentName);
251
+
252
+ if (hookTypesDev !== null) {
253
+ let table = '';
254
+
255
+ const secondColumnStart = 30;
256
+
257
+ for (let i = 0; i <= ((hookTypesUpdateIndexDev: any): number); i++) {
258
+ const oldHookName = hookTypesDev[i];
259
+ const newHookName =
260
+ i === ((hookTypesUpdateIndexDev: any): number)
261
+ ? currentHookName
262
+ : oldHookName;
263
+
264
+ let row = `${i + 1}. ${oldHookName}`;
265
+
266
+ // Extra space so second column lines up
267
+ // lol @ IE not supporting String#repeat
268
+ while (row.length < secondColumnStart) {
269
+ row += ' ';
270
+ }
271
+
272
+ row += newHookName + '\n';
273
+
274
+ table += row;
275
+ }
276
+
277
+ console.error(
278
+ 'React has detected a change in the order of Hooks called by %s. ' +
279
+ 'This will lead to bugs and errors if not fixed. ' +
280
+ 'For more information, read the Rules of Hooks: https://fb.me/rules-of-hooks\n\n' +
281
+ ' Previous render Next render\n' +
282
+ ' ------------------------------------------------------\n' +
283
+ '%s' +
284
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n',
285
+ componentName,
286
+ table,
287
+ );
288
+ }
289
+ }
290
+ }
291
+}
292
+
293
+function throwInvalidHookError() {
294
+ invariant(
295
+ false,
296
+ 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
297
+ ' one of the following reasons:\n' +
298
+ '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
299
+ '2. You might be breaking the Rules of Hooks\n' +
300
+ '3. You might have more than one copy of React in the same app\n' +
301
+ 'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
302
+ );
303
+}
304
+
305
+function areHookInputsEqual(
306
+ nextDeps: Array<mixed>,
307
+ prevDeps: Array<mixed> | null,
308
+) {
309
+ if (__DEV__) {
310
+ if (ignorePreviousDependencies) {
311
+ // Only true when this component is being hot reloaded.
312
+ return false;
313
+ }
314
+ }
315
+
316
+ if (prevDeps === null) {
317
+ if (__DEV__) {
318
+ console.error(
319
+ '%s received a final argument during this render, but not during ' +
320
+ 'the previous render. Even though the final argument is optional, ' +
321
+ 'its type cannot change between renders.',
322
+ currentHookNameInDev,
323
+ );
324
+ }
325
+ return false;
326
+ }
327
+
328
+ if (__DEV__) {
329
+ // Don't bother comparing lengths in prod because these arrays should be
330
+ // passed inline.
331
+ if (nextDeps.length !== prevDeps.length) {
332
+ console.error(
333
+ 'The final argument passed to %s changed size between renders. The ' +
334
+ 'order and size of this array must remain constant.\n\n' +
335
+ 'Previous: %s\n' +
336
+ 'Incoming: %s',
337
+ currentHookNameInDev,
338
+ `[${prevDeps.join(', ')}]`,
339
+ `[${nextDeps.join(', ')}]`,
340
+ );
341
+ }
342
+ }
343
+ for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
344
+ if (is(nextDeps[i], prevDeps[i])) {
345
+ continue;
346
+ }
347
+ return false;
348
+ }
349
+ return true;
350
+}
351
+
352
+export function renderWithHooks<Props, SecondArg>(
353
+ current: Fiber | null,
354
+ workInProgress: Fiber,
355
+ Component: (p: Props, arg: SecondArg) => any,
356
+ props: Props,
357
+ secondArg: SecondArg,
358
+ nextRenderExpirationTime: ExpirationTime,
359
+): any {
360
+ renderExpirationTime = nextRenderExpirationTime;
361
+ currentlyRenderingFiber = workInProgress;
362
+
363
+ if (__DEV__) {
364
+ hookTypesDev =
365
+ current !== null
366
+ ? ((current._debugHookTypes: any): Array<HookType>)
367
+ : null;
368
+ hookTypesUpdateIndexDev = -1;
369
+ // Used for hot reloading:
370
+ ignorePreviousDependencies =
371
+ current !== null && current.type !== workInProgress.type;
372
+ }
373
+
374
+ workInProgress.memoizedState = null;
375
+ workInProgress.updateQueue = null;
376
+ workInProgress.expirationTime = NoWork;
377
+
378
+ // The following should have already been reset
379
+ // currentHook = null;
380
+ // workInProgressHook = null;
381
+
382
+ // didScheduleRenderPhaseUpdate = false;
383
+
384
+ // TODO Warn if no hooks are used at all during mount, then some are used during update.
385
+ // Currently we will identify the update render as a mount because memoizedState === null.
386
+ // This is tricky because it's valid for certain types of components (e.g. React.lazy)
387
+
388
+ // Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used.
389
+ // Non-stateful hooks (e.g. context) don't get added to memoizedState,
390
+ // so memoizedState would be null during updates and mounts.
391
+ if (__DEV__) {
392
+ if (current !== null && current.memoizedState !== null) {
393
+ ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
394
+ } else if (hookTypesDev !== null) {
395
+ // This dispatcher handles an edge case where a component is updating,
396
+ // but no stateful hooks have been used.
397
+ // We want to match the production code behavior (which will use HooksDispatcherOnMount),
398
+ // but with the extra DEV validation to ensure hooks ordering hasn't changed.
399
+ // This dispatcher does that.
400
+ ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV;
401
+ } else {
402
+ ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
403
+ }
404
+ } else {
405
+ ReactCurrentDispatcher.current =
406
+ current === null || current.memoizedState === null
407
+ ? HooksDispatcherOnMount
408
+ : HooksDispatcherOnUpdate;
409
+ }
410
+
411
+ let children = Component(props, secondArg);
412
+
413
+ // Check if there was a render phase update
414
+ if (didScheduleRenderPhaseUpdateDuringThisPass) {
415
+ // Keep rendering in a loop for as long as render phase updates continue to
416
+ // be scheduled. Use a counter to prevent infinite loops.
417
+ let numberOfReRenders: number = 0;
418
+ do {
419
+ didScheduleRenderPhaseUpdateDuringThisPass = false;
420
+ invariant(
421
+ numberOfReRenders < RE_RENDER_LIMIT,
422
+ 'Too many re-renders. React limits the number of renders to prevent ' +
423
+ 'an infinite loop.',
424
+ );
425
+
426
+ numberOfReRenders += 1;
427
+ if (__DEV__) {
428
+ // Even when hot reloading, allow dependencies to stabilize
429
+ // after first render to prevent infinite render phase updates.
430
+ ignorePreviousDependencies = false;
431
+ }
432
+
433
+ // Start over from the beginning of the list
434
+ currentHook = null;
435
+ workInProgressHook = null;
436
+
437
+ workInProgress.updateQueue = null;
438
+
439
+ if (__DEV__) {
440
+ // Also validate hook order for cascading updates.
441
+ hookTypesUpdateIndexDev = -1;
442
+ }
443
+
444
+ ReactCurrentDispatcher.current = __DEV__
445
+ ? HooksDispatcherOnRerenderInDEV
446
+ : HooksDispatcherOnRerender;
447
+
448
+ children = Component(props, secondArg);
449
+ } while (didScheduleRenderPhaseUpdateDuringThisPass);
450
+ }
451
+
452
+ // We can assume the previous dispatcher is always this one, since we set it
453
+ // at the beginning of the render phase and there's no re-entrancy.
454
+ ReactCurrentDispatcher.current = ContextOnlyDispatcher;
455
+
456
+ if (__DEV__) {
457
+ workInProgress._debugHookTypes = hookTypesDev;
458
+ }
459
+
460
+ // This check uses currentHook so that it works the same in DEV and prod bundles.
461
+ // hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.
462
+ const didRenderTooFewHooks =
463
+ currentHook !== null && currentHook.next !== null;
464
+
465
+ renderExpirationTime = NoWork;
466
+ currentlyRenderingFiber = (null: any);
467
+
468
+ currentHook = null;
469
+ workInProgressHook = null;
470
+
471
+ if (__DEV__) {
472
+ currentHookNameInDev = null;
473
+ hookTypesDev = null;
474
+ hookTypesUpdateIndexDev = -1;
475
+ }
476
+
477
+ didScheduleRenderPhaseUpdate = false;
478
+
479
+ invariant(
480
+ !didRenderTooFewHooks,
481
+ 'Rendered fewer hooks than expected. This may be caused by an accidental ' +
482
+ 'early return statement.',
483
+ );
484
+
485
+ return children;
486
+}
487
+
488
+export function bailoutHooks(
489
+ current: Fiber,
490
+ workInProgress: Fiber,
491
+ expirationTime: ExpirationTime,
492
+) {
493
+ workInProgress.updateQueue = current.updateQueue;
494
+ workInProgress.effectTag &= ~(PassiveEffect | UpdateEffect);
495
+ if (current.expirationTime <= expirationTime) {
496
+ current.expirationTime = NoWork;
497
+ }
498
+}
499
+
500
+export function resetHooksAfterThrow(): void {
501
+ // We can assume the previous dispatcher is always this one, since we set it
502
+ // at the beginning of the render phase and there's no re-entrancy.
503
+ ReactCurrentDispatcher.current = ContextOnlyDispatcher;
504
+
505
+ if (didScheduleRenderPhaseUpdate) {
506
+ // There were render phase updates. These are only valid for this render
507
+ // phase, which we are now aborting. Remove the updates from the queues so
508
+ // they do not persist to the next render. Do not remove updates from hooks
509
+ // that weren't processed.
510
+ //
511
+ // Only reset the updates from the queue if it has a clone. If it does
512
+ // not have a clone, that means it wasn't processed, and the updates were
513
+ // scheduled before we entered the render phase.
514
+ let hook: Hook | null = currentlyRenderingFiber.memoizedState;
515
+ while (hook !== null) {
516
+ const queue = hook.queue;
517
+ if (queue !== null) {
518
+ queue.pending = null;
519
+ }
520
+ hook = hook.next;
521
+ }
522
+ didScheduleRenderPhaseUpdate = false;
523
+ }
524
+
525
+ renderExpirationTime = NoWork;
526
+ currentlyRenderingFiber = (null: any);
527
+
528
+ currentHook = null;
529
+ workInProgressHook = null;
530
+
531
+ if (__DEV__) {
532
+ hookTypesDev = null;
533
+ hookTypesUpdateIndexDev = -1;
534
+
535
+ currentHookNameInDev = null;
536
+
537
+ isUpdatingOpaqueValueInRenderPhase = false;
538
+ }
539
+
540
+ didScheduleRenderPhaseUpdateDuringThisPass = false;
541
+}
542
+
543
+function mountWorkInProgressHook(): Hook {
544
+ const hook: Hook = {
545
+ memoizedState: null,
546
+
547
+ baseState: null,
548
+ baseQueue: null,
549
+ queue: null,
550
+
551
+ next: null,
552
+ };
553
+
554
+ if (workInProgressHook === null) {
555
+ // This is the first hook in the list
556
+ currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
557
+ } else {
558
+ // Append to the end of the list
559
+ workInProgressHook = workInProgressHook.next = hook;
560
+ }
561
+ return workInProgressHook;
562
+}
563
+
564
+function updateWorkInProgressHook(): Hook {
565
+ // This function is used both for updates and for re-renders triggered by a
566
+ // render phase update. It assumes there is either a current hook we can
567
+ // clone, or a work-in-progress hook from a previous render pass that we can
568
+ // use as a base. When we reach the end of the base list, we must switch to
569
+ // the dispatcher used for mounts.
570
+ let nextCurrentHook: null | Hook;
571
+ if (currentHook === null) {
572
+ const current = currentlyRenderingFiber.alternate;
573
+ if (current !== null) {
574
+ nextCurrentHook = current.memoizedState;
575
+ } else {
576
+ nextCurrentHook = null;
577
+ }
578
+ } else {
579
+ nextCurrentHook = currentHook.next;
580
+ }
581
+
582
+ let nextWorkInProgressHook: null | Hook;
583
+ if (workInProgressHook === null) {
584
+ nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
585
+ } else {
586
+ nextWorkInProgressHook = workInProgressHook.next;
587
+ }
588
+
589
+ if (nextWorkInProgressHook !== null) {
590
+ // There's already a work-in-progress. Reuse it.
591
+ workInProgressHook = nextWorkInProgressHook;
592
+ nextWorkInProgressHook = workInProgressHook.next;
593
+
594
+ currentHook = nextCurrentHook;
595
+ } else {
596
+ // Clone from the current hook.
597
+
598
+ invariant(
599
+ nextCurrentHook !== null,
600
+ 'Rendered more hooks than during the previous render.',
601
+ );
602
+ currentHook = nextCurrentHook;
603
+
604
+ const newHook: Hook = {
605
+ memoizedState: currentHook.memoizedState,
606
+
607
+ baseState: currentHook.baseState,
608
+ baseQueue: currentHook.baseQueue,
609
+ queue: currentHook.queue,
610
+
611
+ next: null,
612
+ };
613
+
614
+ if (workInProgressHook === null) {
615
+ // This is the first hook in the list.
616
+ currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
617
+ } else {
618
+ // Append to the end of the list.
619
+ workInProgressHook = workInProgressHook.next = newHook;
620
+ }
621
+ }
622
+ return workInProgressHook;
623
+}
624
+
625
+function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
626
+ return {
627
+ lastEffect: null,
628
+ };
629
+}
630
+
631
+function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
632
+ // $FlowFixMe: Flow doesn't like mixed types
633
+ return typeof action === 'function' ? action(state) : action;
634
+}
635
+
636
+function mountReducer<S, I, A>(
637
+ reducer: (S, A) => S,
638
+ initialArg: I,
639
+ init?: I => S,
640
+): [S, Dispatch<A>] {
641
+ const hook = mountWorkInProgressHook();
642
+ let initialState;
643
+ if (init !== undefined) {
644
+ initialState = init(initialArg);
645
+ } else {
646
+ initialState = ((initialArg: any): S);
647
+ }
648
+ hook.memoizedState = hook.baseState = initialState;
649
+ const queue = (hook.queue = {
650
+ pending: null,
651
+ dispatch: null,
652
+ lastRenderedReducer: reducer,
653
+ lastRenderedState: (initialState: any),
654
+ });
655
+ const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
656
+ null,
657
+ currentlyRenderingFiber,
658
+ queue,
659
+ ): any));
660
+ return [hook.memoizedState, dispatch];
661
+}
662
+
663
+function updateReducer<S, I, A>(
664
+ reducer: (S, A) => S,
665
+ initialArg: I,
666
+ init?: I => S,
667
+): [S, Dispatch<A>] {
668
+ const hook = updateWorkInProgressHook();
669
+ const queue = hook.queue;
670
+ invariant(
671
+ queue !== null,
672
+ 'Should have a queue. This is likely a bug in React. Please file an issue.',
673
+ );
674
+
675
+ queue.lastRenderedReducer = reducer;
676
+
677
+ const current: Hook = (currentHook: any);
678
+
679
+ // The last rebase update that is NOT part of the base state.
680
+ let baseQueue = current.baseQueue;
681
+
682
+ // The last pending update that hasn't been processed yet.
683
+ const pendingQueue = queue.pending;
684
+ if (pendingQueue !== null) {
685
+ // We have new updates that haven't been processed yet.
686
+ // We'll add them to the base queue.
687
+ if (baseQueue !== null) {
688
+ // Merge the pending queue and the base queue.
689
+ const baseFirst = baseQueue.next;
690
+ const pendingFirst = pendingQueue.next;
691
+ baseQueue.next = pendingFirst;
692
+ pendingQueue.next = baseFirst;
693
+ }
694
+ if (__DEV__) {
695
+ if (current.baseQueue !== baseQueue) {
696
+ // Internal invariant that should never happen, but feasibly could in
697
+ // the future if we implement resuming, or some form of that.
698
+ console.error(
699
+ 'Internal error: Expected work-in-progress queue to be a clone. ' +
700
+ 'This is a bug in React.',
701
+ );
702
+ }
703
+ }
704
+ current.baseQueue = baseQueue = pendingQueue;
705
+ queue.pending = null;
706
+ }
707
+
708
+ if (baseQueue !== null) {
709
+ // We have a queue to process.
710
+ const first = baseQueue.next;
711
+ let newState = current.baseState;
712
+
713
+ let newBaseState = null;
714
+ let newBaseQueueFirst = null;
715
+ let newBaseQueueLast = null;
716
+ let update = first;
717
+ do {
718
+ const updateExpirationTime = update.expirationTime;
719
+ if (updateExpirationTime < renderExpirationTime) {
720
+ // Priority is insufficient. Skip this update. If this is the first
721
+ // skipped update, the previous update/state is the new base
722
+ // update/state.
723
+ const clone: Update<S, A> = {
724
+ expirationTime: update.expirationTime,
725
+ suspenseConfig: update.suspenseConfig,
726
+ action: update.action,
727
+ eagerReducer: update.eagerReducer,
728
+ eagerState: update.eagerState,
729
+ next: (null: any),
730
+ };
731
+ if (newBaseQueueLast === null) {
732
+ newBaseQueueFirst = newBaseQueueLast = clone;
733
+ newBaseState = newState;
734
+ } else {
735
+ newBaseQueueLast = newBaseQueueLast.next = clone;
736
+ }
737
+ // Update the remaining priority in the queue.
738
+ if (updateExpirationTime > currentlyRenderingFiber.expirationTime) {
739
+ currentlyRenderingFiber.expirationTime = updateExpirationTime;
740
+ markUnprocessedUpdateTime(updateExpirationTime);
741
+ }
742
+ } else {
743
+ // This update does have sufficient priority.
744
+
745
+ if (newBaseQueueLast !== null) {
746
+ const clone: Update<S, A> = {
747
+ expirationTime: Sync, // This update is going to be committed so we never want uncommit it.
748
+ suspenseConfig: update.suspenseConfig,
749
+ action: update.action,
750
+ eagerReducer: update.eagerReducer,
751
+ eagerState: update.eagerState,
752
+ next: (null: any),
753
+ };
754
+ newBaseQueueLast = newBaseQueueLast.next = clone;
755
+ }
756
+
757
+ // Mark the event time of this update as relevant to this render pass.
758
+ // TODO: This should ideally use the true event time of this update rather than
759
+ // its priority which is a derived and not reverseable value.
760
+ // TODO: We should skip this update if it was already committed but currently
761
+ // we have no way of detecting the difference between a committed and suspended
762
+ // update here.
763
+ markRenderEventTimeAndConfig(
764
+ updateExpirationTime,
765
+ update.suspenseConfig,
766
+ );
767
+
768
+ // Process this update.
769
+ if (update.eagerReducer === reducer) {
770
+ // If this update was processed eagerly, and its reducer matches the
771
+ // current reducer, we can use the eagerly computed state.
772
+ newState = ((update.eagerState: any): S);
773
+ } else {
774
+ const action = update.action;
775
+ newState = reducer(newState, action);
776
+ }
777
+ }
778
+ update = update.next;
779
+ } while (update !== null && update !== first);
780
+
781
+ if (newBaseQueueLast === null) {
782
+ newBaseState = newState;
783
+ } else {
784
+ newBaseQueueLast.next = (newBaseQueueFirst: any);
785
+ }
786
+
787
+ // Mark that the fiber performed work, but only if the new state is
788
+ // different from the current state.
789
+ if (!is(newState, hook.memoizedState)) {
790
+ markWorkInProgressReceivedUpdate();
791
+ }
792
+
793
+ hook.memoizedState = newState;
794
+ hook.baseState = newBaseState;
795
+ hook.baseQueue = newBaseQueueLast;
796
+
797
+ queue.lastRenderedState = newState;
798
+ }
799
+
800
+ const dispatch: Dispatch<A> = (queue.dispatch: any);
801
+ return [hook.memoizedState, dispatch];
802
+}
803
+
804
+function rerenderReducer<S, I, A>(
805
+ reducer: (S, A) => S,
806
+ initialArg: I,
807
+ init?: I => S,
808
+): [S, Dispatch<A>] {
809
+ const hook = updateWorkInProgressHook();
810
+ const queue = hook.queue;
811
+ invariant(
812
+ queue !== null,
813
+ 'Should have a queue. This is likely a bug in React. Please file an issue.',
814
+ );
815
+
816
+ queue.lastRenderedReducer = reducer;
817
+
818
+ // This is a re-render. Apply the new render phase updates to the previous
819
+ // work-in-progress hook.
820
+ const dispatch: Dispatch<A> = (queue.dispatch: any);
821
+ const lastRenderPhaseUpdate = queue.pending;
822
+ let newState = hook.memoizedState;
823
+ if (lastRenderPhaseUpdate !== null) {
824
+ // The queue doesn't persist past this render pass.
825
+ queue.pending = null;
826
+
827
+ const firstRenderPhaseUpdate = lastRenderPhaseUpdate.next;
828
+ let update = firstRenderPhaseUpdate;
829
+ do {
830
+ // Process this render phase update. We don't have to check the
831
+ // priority because it will always be the same as the current
832
+ // render's.
833
+ const action = update.action;
834
+ newState = reducer(newState, action);
835
+ update = update.next;
836
+ } while (update !== firstRenderPhaseUpdate);
837
+
838
+ // Mark that the fiber performed work, but only if the new state is
839
+ // different from the current state.
840
+ if (!is(newState, hook.memoizedState)) {
841
+ markWorkInProgressReceivedUpdate();
842
+ }
843
+
844
+ hook.memoizedState = newState;
845
+ // Don't persist the state accumulated from the render phase updates to
846
+ // the base state unless the queue is empty.
847
+ // TODO: Not sure if this is the desired semantics, but it's what we
848
+ // do for gDSFP. I can't remember why.
849
+ if (hook.baseQueue === null) {
850
+ hook.baseState = newState;
851
+ }
852
+
853
+ queue.lastRenderedState = newState;
854
+ }
855
+ return [newState, dispatch];
856
+}
857
+
858
+type MutableSourceMemoizedState<Source, Snapshot> = {|
859
+ refs: {
860
+ getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
861
+ setSnapshot: Snapshot => void,
862
+ },
863
+ source: MutableSource<any>,
864
+ subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
865
+|};
866
+
867
+function readFromUnsubcribedMutableSource<Source, Snapshot>(
868
+ root: FiberRoot,
869
+ source: MutableSource<Source>,
870
+ getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
871
+): Snapshot {
872
+ if (__DEV__) {
873
+ warnAboutMultipleRenderersDEV(source);
874
+ }
875
+
876
+ const getVersion = source._getVersion;
877
+ const version = getVersion(source._source);
878
+
879
+ // Is it safe for this component to read from this source during the current render?
880
+ let isSafeToReadFromSource = false;
881
+
882
+ // Check the version first.
883
+ // If this render has already been started with a specific version,
884
+ // we can use it alone to determine if we can safely read from the source.
885
+ const currentRenderVersion = getWorkInProgressVersion(source);
886
+ if (currentRenderVersion !== null) {
887
+ isSafeToReadFromSource = currentRenderVersion === version;
888
+ } else {
889
+ // If there's no version, then we should fallback to checking the update time.
890
+ const pendingExpirationTime = getLastPendingExpirationTime(root);
891
+
892
+ if (pendingExpirationTime === NoWork) {
893
+ isSafeToReadFromSource = true;
894
+ } else {
895
+ // If the source has pending updates, we can use the current render's expiration
896
+ // time to determine if it's safe to read again from the source.
897
+ isSafeToReadFromSource = pendingExpirationTime >= renderExpirationTime;
898
+ }
899
+
900
+ if (isSafeToReadFromSource) {
901
+ // If it's safe to read from this source during the current render,
902
+ // store the version in case other components read from it.
903
+ // A changed version number will let those components know to throw and restart the render.
904
+ setWorkInProgressVersion(source, version);
905
+ }
906
+ }
907
+
908
+ if (isSafeToReadFromSource) {
909
+ return getSnapshot(source._source);
910
+ } else {
911
+ // This handles the special case of a mutable source being shared beween renderers.
912
+ // In that case, if the source is mutated between the first and second renderer,
913
+ // The second renderer don't know that it needs to reset the WIP version during unwind,
914
+ // (because the hook only marks sources as dirty if it's written to their WIP version).
915
+ // That would cause this tear check to throw again and eventually be visible to the user.
916
+ // We can avoid this infinite loop by explicitly marking the source as dirty.
917
+ //
918
+ // This can lead to tearing in the first renderer when it resumes,
919
+ // but there's nothing we can do about that (short of throwing here and refusing to continue the render).
920
+ markSourceAsDirty(source);
921
+
922
+ invariant(
923
+ false,
924
+ 'Cannot read from mutable source during the current render without tearing. This is a bug in React. Please file an issue.',
925
+ );
926
+ }
927
+}
928
+
929
+function useMutableSource<Source, Snapshot>(
930
+ hook: Hook,
931
+ source: MutableSource<Source>,
932
+ getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
933
+ subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
934
+): Snapshot {
935
+ const root = ((getWorkInProgressRoot(): any): FiberRoot);
936
+ invariant(
937
+ root !== null,
938
+ 'Expected a work-in-progress root. This is a bug in React. Please file an issue.',
939
+ );
940
+
941
+ const getVersion = source._getVersion;
942
+ const version = getVersion(source._source);
943
+
944
+ const dispatcher = ReactCurrentDispatcher.current;
945
+
946
+ // eslint-disable-next-line prefer-const
947
+ let [currentSnapshot, setSnapshot] = dispatcher.useState(() =>
948
+ readFromUnsubcribedMutableSource(root, source, getSnapshot),
949
+ );
950
+ let snapshot = currentSnapshot;
951
+
952
+ // Grab a handle to the state hook as well.
953
+ // We use it to clear the pending update queue if we have a new source.
954
+ const stateHook = ((workInProgressHook: any): Hook);
955
+
956
+ const memoizedState = ((hook.memoizedState: any): MutableSourceMemoizedState<
957
+ Source,
958
+ Snapshot,
959
+ >);
960
+ const refs = memoizedState.refs;
961
+ const prevGetSnapshot = refs.getSnapshot;
962
+ const prevSource = memoizedState.source;
963
+ const prevSubscribe = memoizedState.subscribe;
964
+
965
+ const fiber = currentlyRenderingFiber;
966
+
967
+ hook.memoizedState = ({
968
+ refs,
969
+ source,
970
+ subscribe,
971
+ }: MutableSourceMemoizedState<Source, Snapshot>);
972
+
973
+ // Sync the values needed by our subscription handler after each commit.
974
+ dispatcher.useEffect(() => {
975
+ refs.getSnapshot = getSnapshot;
976
+
977
+ // Normally the dispatch function for a state hook never changes,
978
+ // but this hook recreates the queue in certain cases to avoid updates from stale sources.
979
+ // handleChange() below needs to reference the dispatch function without re-subscribing,
980
+ // so we use a ref to ensure that it always has the latest version.
981
+ refs.setSnapshot = setSnapshot;
982
+
983
+ // Check for a possible change between when we last rendered now.
984
+ const maybeNewVersion = getVersion(source._source);
985
+ if (!is(version, maybeNewVersion)) {
986
+ const maybeNewSnapshot = getSnapshot(source._source);
987
+ if (!is(snapshot, maybeNewSnapshot)) {
988
+ setSnapshot(maybeNewSnapshot);
989
+
990
+ const currentTime = requestCurrentTimeForUpdate();
991
+ const suspenseConfig = requestCurrentSuspenseConfig();
992
+ const expirationTime = computeExpirationForFiber(
993
+ currentTime,
994
+ fiber,
995
+ suspenseConfig,
996
+ );
997
+ setPendingExpirationTime(root, expirationTime);
998
+
999
+ // If the source mutated between render and now,
1000
+ // there may be state updates already scheduled from the old getSnapshot.
1001
+ // Those updates should not commit without this value.
1002
+ // There is no mechanism currently to associate these updates though,
1003
+ // so for now we fall back to synchronously flushing all pending updates.
1004
+ // TODO: Improve this later.
1005
+ markRootExpiredAtTime(root, getLastPendingExpirationTime(root));
1006
+ }
1007
+ }
1008
+ }, [getSnapshot, source, subscribe]);
1009
+
1010
+ // If we got a new source or subscribe function, re-subscribe in a passive effect.
1011
+ dispatcher.useEffect(() => {
1012
+ const handleChange = () => {
1013
+ const latestGetSnapshot = refs.getSnapshot;
1014
+ const latestSetSnapshot = refs.setSnapshot;
1015
+
1016
+ try {
1017
+ latestSetSnapshot(latestGetSnapshot(source._source));
1018
+
1019
+ // Record a pending mutable source update with the same expiration time.
1020
+ const currentTime = requestCurrentTimeForUpdate();
1021
+ const suspenseConfig = requestCurrentSuspenseConfig();
1022
+ const expirationTime = computeExpirationForFiber(
1023
+ currentTime,
1024
+ fiber,
1025
+ suspenseConfig,
1026
+ );
1027
+
1028
+ setPendingExpirationTime(root, expirationTime);
1029
+ } catch (error) {
1030
+ // A selector might throw after a source mutation.
1031
+ // e.g. it might try to read from a part of the store that no longer exists.
1032
+ // In this case we should still schedule an update with React.
1033
+ // Worst case the selector will throw again and then an error boundary will handle it.
1034
+ latestSetSnapshot(
1035
+ (() => {
1036
+ throw error;
1037
+ }: any),
1038
+ );
1039
+ }
1040
+ };
1041
+
1042
+ const unsubscribe = subscribe(source._source, handleChange);
1043
+ if (__DEV__) {
1044
+ if (typeof unsubscribe !== 'function') {
1045
+ console.error(
1046
+ 'Mutable source subscribe function must return an unsubscribe function.',
1047
+ );
1048
+ }
1049
+ }
1050
+
1051
+ return unsubscribe;
1052
+ }, [source, subscribe]);
1053
+
1054
+ // If any of the inputs to useMutableSource change, reading is potentially unsafe.
1055
+ //
1056
+ // If either the source or the subscription have changed we can't can't trust the update queue.
1057
+ // Maybe the source changed in a way that the old subscription ignored but the new one depends on.
1058
+ //
1059
+ // If the getSnapshot function changed, we also shouldn't rely on the update queue.
1060
+ // It's possible that the underlying source was mutated between the when the last "change" event fired,
1061
+ // and when the current render (with the new getSnapshot function) is processed.
1062
+ //
1063
+ // In both cases, we need to throw away pending udpates (since they are no longer relevant)
1064
+ // and treat reading from the source as we do in the mount case.
1065
+ if (
1066
+ !is(prevGetSnapshot, getSnapshot) ||
1067
+ !is(prevSource, source) ||
1068
+ !is(prevSubscribe, subscribe)
1069
+ ) {
1070
+ // Create a new queue and setState method,
1071
+ // So if there are interleaved updates, they get pushed to the older queue.
1072
+ // When this becomes current, the previous queue and dispatch method will be discarded,
1073
+ // including any interleaving updates that occur.
1074
+ const newQueue = {
1075
+ pending: null,
1076
+ dispatch: null,
1077
+ lastRenderedReducer: basicStateReducer,
1078
+ lastRenderedState: snapshot,
1079
+ };
1080
+ newQueue.dispatch = setSnapshot = (dispatchAction.bind(
1081
+ null,
1082
+ currentlyRenderingFiber,
1083
+ newQueue,
1084
+ ): any);
1085
+ stateHook.queue = newQueue;
1086
+ stateHook.baseQueue = null;
1087
+ snapshot = readFromUnsubcribedMutableSource(root, source, getSnapshot);
1088
+ stateHook.memoizedState = stateHook.baseState = snapshot;
1089
+ }
1090
+
1091
+ return snapshot;
1092
+}
1093
+
1094
+function mountMutableSource<Source, Snapshot>(
1095
+ source: MutableSource<Source>,
1096
+ getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
1097
+ subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
1098
+): Snapshot {
1099
+ const hook = mountWorkInProgressHook();
1100
+ hook.memoizedState = ({
1101
+ refs: {
1102
+ getSnapshot,
1103
+ setSnapshot: (null: any),
1104
+ },
1105
+ source,
1106
+ subscribe,
1107
+ }: MutableSourceMemoizedState<Source, Snapshot>);
1108
+ return useMutableSource(hook, source, getSnapshot, subscribe);
1109
+}
1110
+
1111
+function updateMutableSource<Source, Snapshot>(
1112
+ source: MutableSource<Source>,
1113
+ getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
1114
+ subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
1115
+): Snapshot {
1116
+ const hook = updateWorkInProgressHook();
1117
+ return useMutableSource(hook, source, getSnapshot, subscribe);
1118
+}
1119
+
1120
+function mountState<S>(
1121
+ initialState: (() => S) | S,
1122
+): [S, Dispatch<BasicStateAction<S>>] {
1123
+ const hook = mountWorkInProgressHook();
1124
+ if (typeof initialState === 'function') {
1125
+ // $FlowFixMe: Flow doesn't like mixed types
1126
+ initialState = initialState();
1127
+ }
1128
+ hook.memoizedState = hook.baseState = initialState;
1129
+ const queue = (hook.queue = {
1130
+ pending: null,
1131
+ dispatch: null,
1132
+ lastRenderedReducer: basicStateReducer,
1133
+ lastRenderedState: (initialState: any),
1134
+ });
1135
+ const dispatch: Dispatch<
1136
+ BasicStateAction<S>,
1137
+ > = (queue.dispatch = (dispatchAction.bind(
1138
+ null,
1139
+ currentlyRenderingFiber,
1140
+ queue,
1141
+ ): any));
1142
+ return [hook.memoizedState, dispatch];
1143
+}
1144
+
1145
+function updateState<S>(
1146
+ initialState: (() => S) | S,
1147
+): [S, Dispatch<BasicStateAction<S>>] {
1148
+ return updateReducer(basicStateReducer, (initialState: any));
1149
+}
1150
+
1151
+function rerenderState<S>(
1152
+ initialState: (() => S) | S,
1153
+): [S, Dispatch<BasicStateAction<S>>] {
1154
+ return rerenderReducer(basicStateReducer, (initialState: any));
1155
+}
1156
+
1157
+function pushEffect(tag, create, destroy, deps) {
1158
+ const effect: Effect = {
1159
+ tag,
1160
+ create,
1161
+ destroy,
1162
+ deps,
1163
+ // Circular
1164
+ next: (null: any),
1165
+ };
1166
+ let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
1167
+ if (componentUpdateQueue === null) {
1168
+ componentUpdateQueue = createFunctionComponentUpdateQueue();
1169
+ currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
1170
+ componentUpdateQueue.lastEffect = effect.next = effect;
1171
+ } else {
1172
+ const lastEffect = componentUpdateQueue.lastEffect;
1173
+ if (lastEffect === null) {
1174
+ componentUpdateQueue.lastEffect = effect.next = effect;
1175
+ } else {
1176
+ const firstEffect = lastEffect.next;
1177
+ lastEffect.next = effect;
1178
+ effect.next = firstEffect;
1179
+ componentUpdateQueue.lastEffect = effect;
1180
+ }
1181
+ }
1182
+ return effect;
1183
+}
1184
+
1185
+function mountRef<T>(initialValue: T): {|current: T|} {
1186
+ const hook = mountWorkInProgressHook();
1187
+ const ref = {current: initialValue};
1188
+ if (__DEV__) {
1189
+ Object.seal(ref);
1190
+ }
1191
+ hook.memoizedState = ref;
1192
+ return ref;
1193
+}
1194
+
1195
+function updateRef<T>(initialValue: T): {|current: T|} {
1196
+ const hook = updateWorkInProgressHook();
1197
+ return hook.memoizedState;
1198
+}
1199
+
1200
+function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
1201
+ const hook = mountWorkInProgressHook();
1202
+ const nextDeps = deps === undefined ? null : deps;
1203
+ currentlyRenderingFiber.effectTag |= fiberEffectTag;
1204
+ hook.memoizedState = pushEffect(
1205
+ HookHasEffect | hookEffectTag,
1206
+ create,
1207
+ undefined,
1208
+ nextDeps,
1209
+ );
1210
+}
1211
+
1212
+function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
1213
+ const hook = updateWorkInProgressHook();
1214
+ const nextDeps = deps === undefined ? null : deps;
1215
+ let destroy = undefined;
1216
+
1217
+ if (currentHook !== null) {
1218
+ const prevEffect = currentHook.memoizedState;
1219
+ destroy = prevEffect.destroy;
1220
+ if (nextDeps !== null) {
1221
+ const prevDeps = prevEffect.deps;
1222
+ if (areHookInputsEqual(nextDeps, prevDeps)) {
1223
+ pushEffect(hookEffectTag, create, destroy, nextDeps);
1224
+ return;
1225
+ }
1226
+ }
1227
+ }
1228
+
1229
+ currentlyRenderingFiber.effectTag |= fiberEffectTag;
1230
+
1231
+ hook.memoizedState = pushEffect(
1232
+ HookHasEffect | hookEffectTag,
1233
+ create,
1234
+ destroy,
1235
+ nextDeps,
1236
+ );
1237
+}
1238
+
1239
+function mountEffect(
1240
+ create: () => (() => void) | void,
1241
+ deps: Array<mixed> | void | null,
1242
+): void {
1243
+ if (__DEV__) {
1244
+ // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
1245
+ if ('undefined' !== typeof jest) {
1246
+ warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
1247
+ }
1248
+ }
1249
+ return mountEffectImpl(
1250
+ UpdateEffect | PassiveEffect,
1251
+ HookPassive,
1252
+ create,
1253
+ deps,
1254
+ );
1255
+}
1256
+
1257
+function updateEffect(
1258
+ create: () => (() => void) | void,
1259
+ deps: Array<mixed> | void | null,
1260
+): void {
1261
+ if (__DEV__) {
1262
+ // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
1263
+ if ('undefined' !== typeof jest) {
1264
+ warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
1265
+ }
1266
+ }
1267
+ return updateEffectImpl(
1268
+ UpdateEffect | PassiveEffect,
1269
+ HookPassive,
1270
+ create,
1271
+ deps,
1272
+ );
1273
+}
1274
+
1275
+function mountLayoutEffect(
1276
+ create: () => (() => void) | void,
1277
+ deps: Array<mixed> | void | null,
1278
+): void {
1279
+ return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
1280
+}
1281
+
1282
+function updateLayoutEffect(
1283
+ create: () => (() => void) | void,
1284
+ deps: Array<mixed> | void | null,
1285
+): void {
1286
+ return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
1287
+}
1288
+
1289
+function imperativeHandleEffect<T>(
1290
+ create: () => T,
1291
+ ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
1292
+) {
1293
+ if (typeof ref === 'function') {
1294
+ const refCallback = ref;
1295
+ const inst = create();
1296
+ refCallback(inst);
1297
+ return () => {
1298
+ refCallback(null);
1299
+ };
1300
+ } else if (ref !== null && ref !== undefined) {
1301
+ const refObject = ref;
1302
+ if (__DEV__) {
1303
+ if (!refObject.hasOwnProperty('current')) {
1304
+ console.error(
1305
+ 'Expected useImperativeHandle() first argument to either be a ' +
1306
+ 'ref callback or React.createRef() object. Instead received: %s.',
1307
+ 'an object with keys {' + Object.keys(refObject).join(', ') + '}',
1308
+ );
1309
+ }
1310
+ }
1311
+ const inst = create();
1312
+ refObject.current = inst;
1313
+ return () => {
1314
+ refObject.current = null;
1315
+ };
1316
+ }
1317
+}
1318
+
1319
+function mountImperativeHandle<T>(
1320
+ ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
1321
+ create: () => T,
1322
+ deps: Array<mixed> | void | null,
1323
+): void {
1324
+ if (__DEV__) {
1325
+ if (typeof create !== 'function') {
1326
+ console.error(
1327
+ 'Expected useImperativeHandle() second argument to be a function ' +
1328
+ 'that creates a handle. Instead received: %s.',
1329
+ create !== null ? typeof create : 'null',
1330
+ );
1331
+ }
1332
+ }
1333
+
1334
+ // TODO: If deps are provided, should we skip comparing the ref itself?
1335
+ const effectDeps =
1336
+ deps !== null && deps !== undefined ? deps.concat([ref]) : null;
1337
+
1338
+ return mountEffectImpl(
1339
+ UpdateEffect,
1340
+ HookLayout,
1341
+ imperativeHandleEffect.bind(null, create, ref),
1342
+ effectDeps,
1343
+ );
1344
+}
1345
+
1346
+function updateImperativeHandle<T>(
1347
+ ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
1348
+ create: () => T,
1349
+ deps: Array<mixed> | void | null,
1350
+): void {
1351
+ if (__DEV__) {
1352
+ if (typeof create !== 'function') {
1353
+ console.error(
1354
+ 'Expected useImperativeHandle() second argument to be a function ' +
1355
+ 'that creates a handle. Instead received: %s.',
1356
+ create !== null ? typeof create : 'null',
1357
+ );
1358
+ }
1359
+ }
1360
+
1361
+ // TODO: If deps are provided, should we skip comparing the ref itself?
1362
+ const effectDeps =
1363
+ deps !== null && deps !== undefined ? deps.concat([ref]) : null;
1364
+
1365
+ return updateEffectImpl(
1366
+ UpdateEffect,
1367
+ HookLayout,
1368
+ imperativeHandleEffect.bind(null, create, ref),
1369
+ effectDeps,
1370
+ );
1371
+}
1372
+
1373
+function mountDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
1374
+ // This hook is normally a no-op.
1375
+ // The react-debug-hooks package injects its own implementation
1376
+ // so that e.g. DevTools can display custom hook values.
1377
+}
1378
+
1379
+const updateDebugValue = mountDebugValue;
1380
+
1381
+function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
1382
+ const hook = mountWorkInProgressHook();
1383
+ const nextDeps = deps === undefined ? null : deps;
1384
+ hook.memoizedState = [callback, nextDeps];
1385
+ return callback;
1386
+}
1387
+
1388
+function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
1389
+ const hook = updateWorkInProgressHook();
1390
+ const nextDeps = deps === undefined ? null : deps;
1391
+ const prevState = hook.memoizedState;
1392
+ if (prevState !== null) {
1393
+ if (nextDeps !== null) {
1394
+ const prevDeps: Array<mixed> | null = prevState[1];
1395
+ if (areHookInputsEqual(nextDeps, prevDeps)) {
1396
+ return prevState[0];
1397
+ }
1398
+ }
1399
+ }
1400
+ hook.memoizedState = [callback, nextDeps];
1401
+ return callback;
1402
+}
1403
+
1404
+function mountMemo<T>(
1405
+ nextCreate: () => T,
1406
+ deps: Array<mixed> | void | null,
1407
+): T {
1408
+ const hook = mountWorkInProgressHook();
1409
+ const nextDeps = deps === undefined ? null : deps;
1410
+ const nextValue = nextCreate();
1411
+ hook.memoizedState = [nextValue, nextDeps];
1412
+ return nextValue;
1413
+}
1414
+
1415
+function updateMemo<T>(
1416
+ nextCreate: () => T,
1417
+ deps: Array<mixed> | void | null,
1418
+): T {
1419
+ const hook = updateWorkInProgressHook();
1420
+ const nextDeps = deps === undefined ? null : deps;
1421
+ const prevState = hook.memoizedState;
1422
+ if (prevState !== null) {
1423
+ // Assume these are defined. If they're not, areHookInputsEqual will warn.
1424
+ if (nextDeps !== null) {
1425
+ const prevDeps: Array<mixed> | null = prevState[1];
1426
+ if (areHookInputsEqual(nextDeps, prevDeps)) {
1427
+ return prevState[0];
1428
+ }
1429
+ }
1430
+ }
1431
+ const nextValue = nextCreate();
1432
+ hook.memoizedState = [nextValue, nextDeps];
1433
+ return nextValue;
1434
+}
1435
+
1436
+function mountDeferredValue<T>(
1437
+ value: T,
1438
+ config: TimeoutConfig | void | null,
1439
+): T {
1440
+ const [prevValue, setValue] = mountState(value);
1441
+ mountEffect(() => {
1442
+ const previousConfig = ReactCurrentBatchConfig.suspense;
1443
+ ReactCurrentBatchConfig.suspense = config === undefined ? null : config;
1444
+ try {
1445
+ setValue(value);
1446
+ } finally {
1447
+ ReactCurrentBatchConfig.suspense = previousConfig;
1448
+ }
1449
+ }, [value, config]);
1450
+ return prevValue;
1451
+}
1452
+
1453
+function updateDeferredValue<T>(
1454
+ value: T,
1455
+ config: TimeoutConfig | void | null,
1456
+): T {
1457
+ const [prevValue, setValue] = updateState(value);
1458
+ updateEffect(() => {
1459
+ const previousConfig = ReactCurrentBatchConfig.suspense;
1460
+ ReactCurrentBatchConfig.suspense = config === undefined ? null : config;
1461
+ try {
1462
+ setValue(value);
1463
+ } finally {
1464
+ ReactCurrentBatchConfig.suspense = previousConfig;
1465
+ }
1466
+ }, [value, config]);
1467
+ return prevValue;
1468
+}
1469
+
1470
+function rerenderDeferredValue<T>(
1471
+ value: T,
1472
+ config: TimeoutConfig | void | null,
1473
+): T {
1474
+ const [prevValue, setValue] = rerenderState(value);
1475
+ updateEffect(() => {
1476
+ const previousConfig = ReactCurrentBatchConfig.suspense;
1477
+ ReactCurrentBatchConfig.suspense = config === undefined ? null : config;
1478
+ try {
1479
+ setValue(value);
1480
+ } finally {
1481
+ ReactCurrentBatchConfig.suspense = previousConfig;
1482
+ }
1483
+ }, [value, config]);
1484
+ return prevValue;
1485
+}
1486
+
1487
+function startTransition(setPending, config, callback) {
1488
+ const priorityLevel = getCurrentPriorityLevel();
1489
+ runWithPriority(
1490
+ priorityLevel < UserBlockingPriority ? UserBlockingPriority : priorityLevel,
1491
+ () => {
1492
+ setPending(true);
1493
+ },
1494
+ );
1495
+ runWithPriority(
1496
+ priorityLevel > NormalPriority ? NormalPriority : priorityLevel,
1497
+ () => {
1498
+ const previousConfig = ReactCurrentBatchConfig.suspense;
1499
+ ReactCurrentBatchConfig.suspense = config === undefined ? null : config;
1500
+ try {
1501
+ setPending(false);
1502
+ callback();
1503
+ } finally {
1504
+ ReactCurrentBatchConfig.suspense = previousConfig;
1505
+ }
1506
+ },
1507
+ );
1508
+}
1509
+
1510
+function mountTransition(
1511
+ config: SuspenseConfig | void | null,
1512
+): [(() => void) => void, boolean] {
1513
+ const [isPending, setPending] = mountState(false);
1514
+ const start = mountCallback(startTransition.bind(null, setPending, config), [
1515
+ setPending,
1516
+ config,
1517
+ ]);
1518
+ return [start, isPending];
1519
+}
1520
+
1521
+function updateTransition(
1522
+ config: SuspenseConfig | void | null,
1523
+): [(() => void) => void, boolean] {
1524
+ const [isPending, setPending] = updateState(false);
1525
+ const start = updateCallback(startTransition.bind(null, setPending, config), [
1526
+ setPending,
1527
+ config,
1528
+ ]);
1529
+ return [start, isPending];
1530
+}
1531
+
1532
+function rerenderTransition(
1533
+ config: SuspenseConfig | void | null,
1534
+): [(() => void) => void, boolean] {
1535
+ const [isPending, setPending] = rerenderState(false);
1536
+ const start = updateCallback(startTransition.bind(null, setPending, config), [
1537
+ setPending,
1538
+ config,
1539
+ ]);
1540
+ return [start, isPending];
1541
+}
1542
+
1543
+let isUpdatingOpaqueValueInRenderPhase = false;
1544
+export function getIsUpdatingOpaqueValueInRenderPhaseInDEV(): boolean | void {
1545
+ if (__DEV__) {
1546
+ return isUpdatingOpaqueValueInRenderPhase;
1547
+ }
1548
+}
1549
+
1550
+function warnOnOpaqueIdentifierAccessInDEV(fiber) {
1551
+ if (__DEV__) {
1552
+ // TODO: Should warn in effects and callbacks, too
1553
+ const name = getComponentName(fiber.type) || 'Unknown';
1554
+ if (getIsRendering() && !didWarnAboutUseOpaqueIdentifier[name]) {
1555
+ console.error(
1556
+ 'The object passed back from useOpaqueIdentifier is meant to be ' +
1557
+ 'passed through to attributes only. Do not read the ' +
1558
+ 'value directly.',
1559
+ );
1560
+ didWarnAboutUseOpaqueIdentifier[name] = true;
1561
+ }
1562
+ }
1563
+}
1564
+
1565
+function mountOpaqueIdentifier(): OpaqueIDType | void {
1566
+ const makeId = __DEV__
1567
+ ? makeClientIdInDEV.bind(
1568
+ null,
1569
+ warnOnOpaqueIdentifierAccessInDEV.bind(null, currentlyRenderingFiber),
1570
+ )
1571
+ : makeClientId;
1572
+
1573
+ if (getIsHydrating()) {
1574
+ let didUpgrade = false;
1575
+ const fiber = currentlyRenderingFiber;
1576
+ const readValue = () => {
1577
+ if (!didUpgrade) {
1578
+ // Only upgrade once. This works even inside the render phase because
1579
+ // the update is added to a shared queue, which outlasts the
1580
+ // in-progress render.
1581
+ didUpgrade = true;
1582
+ if (__DEV__) {
1583
+ isUpdatingOpaqueValueInRenderPhase = true;
1584
+ setId(makeId());
1585
+ isUpdatingOpaqueValueInRenderPhase = false;
1586
+ warnOnOpaqueIdentifierAccessInDEV(fiber);
1587
+ } else {
1588
+ setId(makeId());
1589
+ }
1590
+ }
1591
+ invariant(
1592
+ false,
1593
+ 'The object passed back from useOpaqueIdentifier is meant to be ' +
1594
+ 'passed through to attributes only. Do not read the value directly.',
1595
+ );
1596
+ };
1597
+ const id = makeOpaqueHydratingObject(readValue);
1598
+
1599
+ const setId = mountState(id)[1];
1600
+
1601
+ if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {
1602
+ currentlyRenderingFiber.effectTag |= UpdateEffect | PassiveEffect;
1603
+ pushEffect(
1604
+ HookHasEffect | HookPassive,
1605
+ () => {
1606
+ setId(makeId());
1607
+ },
1608
+ undefined,
1609
+ null,
1610
+ );
1611
+ }
1612
+ return id;
1613
+ } else {
1614
+ const id = makeId();
1615
+ mountState(id);
1616
+ return id;
1617
+ }
1618
+}
1619
+
1620
+function updateOpaqueIdentifier(): OpaqueIDType | void {
1621
+ const id = updateState(undefined)[0];
1622
+ return id;
1623
+}
1624
+
1625
+function rerenderOpaqueIdentifier(): OpaqueIDType | void {
1626
+ const id = rerenderState(undefined)[0];
1627
+ return id;
1628
+}
1629
+
1630
+function dispatchAction<S, A>(
1631
+ fiber: Fiber,
1632
+ queue: UpdateQueue<S, A>,
1633
+ action: A,
1634
+) {
1635
+ if (__DEV__) {
1636
+ if (typeof arguments[3] === 'function') {
1637
+ console.error(
1638
+ "State updates from the useState() and useReducer() Hooks don't support the " +
1639
+ 'second callback argument. To execute a side effect after ' +
1640
+ 'rendering, declare it in the component body with useEffect().',
1641
+ );
1642
+ }
1643
+ }
1644
+
1645
+ const currentTime = requestCurrentTimeForUpdate();
1646
+ const suspenseConfig = requestCurrentSuspenseConfig();
1647
+ const expirationTime = computeExpirationForFiber(
1648
+ currentTime,
1649
+ fiber,
1650
+ suspenseConfig,
1651
+ );
1652
+
1653
+ const update: Update<S, A> = {
1654
+ expirationTime,
1655
+ suspenseConfig,
1656
+ action,
1657
+ eagerReducer: null,
1658
+ eagerState: null,
1659
+ next: (null: any),
1660
+ };
1661
+
1662
+ if (__DEV__) {
1663
+ update.priority = getCurrentPriorityLevel();
1664
+ }
1665
+
1666
+ // Append the update to the end of the list.
1667
+ const pending = queue.pending;
1668
+ if (pending === null) {
1669
+ // This is the first update. Create a circular list.
1670
+ update.next = update;
1671
+ } else {
1672
+ update.next = pending.next;
1673
+ pending.next = update;
1674
+ }
1675
+ queue.pending = update;
1676
+
1677
+ const alternate = fiber.alternate;
1678
+ if (
1679
+ fiber === currentlyRenderingFiber ||
1680
+ (alternate !== null && alternate === currentlyRenderingFiber)
1681
+ ) {
1682
+ // This is a render phase update. Stash it in a lazily-created map of
1683
+ // queue -> linked list of updates. After this render pass, we'll restart
1684
+ // and apply the stashed updates on top of the work-in-progress hook.
1685
+ didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
1686
+ update.expirationTime = renderExpirationTime;
1687
+ } else {
1688
+ if (
1689
+ fiber.expirationTime === NoWork &&
1690
+ (alternate === null || alternate.expirationTime === NoWork)
1691
+ ) {
1692
+ // The queue is currently empty, which means we can eagerly compute the
1693
+ // next state before entering the render phase. If the new state is the
1694
+ // same as the current state, we may be able to bail out entirely.
1695
+ const lastRenderedReducer = queue.lastRenderedReducer;
1696
+ if (lastRenderedReducer !== null) {
1697
+ let prevDispatcher;
1698
+ if (__DEV__) {
1699
+ prevDispatcher = ReactCurrentDispatcher.current;
1700
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
1701
+ }
1702
+ try {
1703
+ const currentState: S = (queue.lastRenderedState: any);
1704
+ const eagerState = lastRenderedReducer(currentState, action);
1705
+ // Stash the eagerly computed state, and the reducer used to compute
1706
+ // it, on the update object. If the reducer hasn't changed by the
1707
+ // time we enter the render phase, then the eager state can be used
1708
+ // without calling the reducer again.
1709
+ update.eagerReducer = lastRenderedReducer;
1710
+ update.eagerState = eagerState;
1711
+ if (is(eagerState, currentState)) {
1712
+ // Fast path. We can bail out without scheduling React to re-render.
1713
+ // It's still possible that we'll need to rebase this update later,
1714
+ // if the component re-renders for a different reason and by that
1715
+ // time the reducer has changed.
1716
+ return;
1717
+ }
1718
+ } catch (error) {
1719
+ // Suppress the error. It will throw again in the render phase.
1720
+ } finally {
1721
+ if (__DEV__) {
1722
+ ReactCurrentDispatcher.current = prevDispatcher;
1723
+ }
1724
+ }
1725
+ }
1726
+ }
1727
+ if (__DEV__) {
1728
+ // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
1729
+ if ('undefined' !== typeof jest) {
1730
+ warnIfNotScopedWithMatchingAct(fiber);
1731
+ warnIfNotCurrentlyActingUpdatesInDev(fiber);
1732
+ }
1733
+ }
1734
+ scheduleUpdateOnFiber(fiber, expirationTime);
1735
+ }
1736
+}
1737
+
1738
+const noOpMount = () => {};
1739
+
1740
+function validateNotInFunctionRender(): boolean {
1741
+ if (currentlyRenderingFiber === null) {
1742
+ return true;
1743
+ }
1744
+ if (__DEV__) {
1745
+ console.warn(
1746
+ 'Event listener methods from useEvent() cannot be called during render.' +
1747
+ ' These methods should be called in an effect or event callback outside the render.',
1748
+ );
1749
+ }
1750
+ return false;
1751
+}
1752
+
1753
+function createReactListener(
1754
+ event: ReactListenerEvent,
1755
+ callback: Event => void,
1756
+ target: EventTarget | ReactScopeMethods,
1757
+ destroy: Node => void,
1758
+): ReactListener {
1759
+ return {
1760
+ callback,
1761
+ destroy,
1762
+ event,
1763
+ target,
1764
+ };
1765
+}
1766
+
1767
+function mountEventListener(event: ReactListenerEvent): ReactListenerMap {
1768
+ if (enableUseEventAPI) {
1769
+ const hook = mountWorkInProgressHook();
1770
+ const listenerMap: Map<
1771
+ EventTarget | ReactScopeMethods,
1772
+ ReactListener,
1773
+ > = new Map();
1774
+ const rootContainerInstance = getRootHostContainer();
1775
+
1776
+ // Register the event to the current root to ensure event
1777
+ // replaying can pick up the event ahead of time.
1778
+ registerEvent(event, rootContainerInstance);
1779
+
1780
+ const clear = () => {
1781
+ if (validateNotInFunctionRender()) {
1782
+ const listeners = Array.from(listenerMap.values());
1783
+ for (let i = 0; i < listeners.length; i++) {
1784
+ unmountHostEventListener(listeners[i]);
1785
+ }
1786
+ listenerMap.clear();
1787
+ }
1788
+ };
1789
+
1790
+ const destroy = (target: Node) => {
1791
+ // We don't need to call detachListenerFromInstance
1792
+ // here as this method should only ever be called
1793
+ // from renderers that need to remove the instance
1794
+ // from the map representing an instance that still
1795
+ // holds a reference to the listenerMap. This means
1796
+ // things like "window" listeners on ReactDOM should
1797
+ // never enter this call path as the the instance in
1798
+ // those cases would be that of "window", which
1799
+ // should be handled via an optimized route in the
1800
+ // renderer, making less overhead here. If we change
1801
+ // this heuristic we should update this path to make
1802
+ // sure we call detachListenerFromInstance.
1803
+ listenerMap.delete(target);
1804
+ };
1805
+
1806
+ const reactListenerMap: ReactListenerMap = {
1807
+ clear,
1808
+ setListener(
1809
+ target: EventTarget | ReactScopeMethods,
1810
+ callback: ?(Event) => void,
1811
+ ): void {
1812
+ if (
1813
+ validateNotInFunctionRender() &&
1814
+ validateEventListenerTarget(target, callback)
1815
+ ) {
1816
+ let listener = listenerMap.get(target);
1817
+ if (listener === undefined) {
1818
+ if (callback == null) {
1819
+ return;
1820
+ }
1821
+ listener = createReactListener(event, callback, target, destroy);
1822
+ listenerMap.set(target, listener);
1823
+ } else {
1824
+ if (callback == null) {
1825
+ listenerMap.delete(target);
1826
+ unmountHostEventListener(listener);
1827
+ return;
1828
+ }
1829
+ listener.callback = callback;
1830
+ }
1831
+ mountHostEventListener(listener);
1832
+ }
1833
+ },
1834
+ };
1835
+ // In order to clear up upon the hook unmounting,
1836
+ // we ensure we set the effecrt tag so that we visit
1837
+ // this effect in the commit phase, so we can handle
1838
+ // clean-up accordingly.
1839
+ currentlyRenderingFiber.effectTag |= UpdateEffect;
1840
+ pushEffect(NoHookEffect, noOpMount, clear, null);
1841
+ hook.memoizedState = [reactListenerMap, event, clear];
1842
+ return reactListenerMap;
1843
+ }
1844
+ // To make Flow not complain
1845
+ return (undefined: any);
1846
+}
1847
+
1848
+function updateEventListener(event: ReactListenerEvent): ReactListenerMap {
1849
+ if (enableUseEventAPI) {
1850
+ const hook = updateWorkInProgressHook();
1851
+ const [reactListenerMap, memoizedEvent, clear] = hook.memoizedState;
1852
+ if (__DEV__) {
1853
+ if (memoizedEvent.type !== event.type) {
1854
+ console.warn(
1855
+ 'The event type argument passed to the useEvent() hook was different between renders.' +
1856
+ ' The event type is static and should never change between renders.',
1857
+ );
1858
+ }
1859
+ if (memoizedEvent.capture !== event.capture) {
1860
+ console.warn(
1861
+ 'The "capture" option passed to the useEvent() hook was different between renders.' +
1862
+ ' The "capture" option is static and should never change between renders.',
1863
+ );
1864
+ }
1865
+ if (memoizedEvent.priority !== event.priority) {
1866
+ console.warn(
1867
+ 'The "priority" option passed to the useEvent() hook was different between renders.' +
1868
+ ' The "priority" option is static and should never change between renders.',
1869
+ );
1870
+ }
1871
+ if (memoizedEvent.passive !== event.passive) {
1872
+ console.warn(
1873
+ 'The "passive" option passed to the useEvent() hook was different between renders.' +
1874
+ ' The "passive" option is static and should never change between renders.',
1875
+ );
1876
+ }
1877
+ }
1878
+ // In order to clear up upon the hook unmounting,
1879
+ // we ensure we set the effecrt tag so that we visit
1880
+ // this effect in the commit phase, so we can handle
1881
+ // clean-up accordingly.
1882
+ currentlyRenderingFiber.effectTag |= UpdateEffect;
1883
+ pushEffect(NoHookEffect, noOpMount, clear, null);
1884
+ return reactListenerMap;
1885
+ }
1886
+ // To make Flow not complain
1887
+ return (undefined: any);
1888
+}
1889
+
1890
+export const ContextOnlyDispatcher: Dispatcher = {
1891
+ readContext,
1892
+
1893
+ useCallback: throwInvalidHookError,
1894
+ useContext: throwInvalidHookError,
1895
+ useEffect: throwInvalidHookError,
1896
+ useImperativeHandle: throwInvalidHookError,
1897
+ useLayoutEffect: throwInvalidHookError,
1898
+ useMemo: throwInvalidHookError,
1899
+ useReducer: throwInvalidHookError,
1900
+ useRef: throwInvalidHookError,
1901
+ useState: throwInvalidHookError,
1902
+ useDebugValue: throwInvalidHookError,
1903
+ useResponder: throwInvalidHookError,
1904
+ useDeferredValue: throwInvalidHookError,
1905
+ useTransition: throwInvalidHookError,
1906
+ useMutableSource: throwInvalidHookError,
1907
+ useEvent: throwInvalidHookError,
1908
+ useOpaqueIdentifier: throwInvalidHookError,
1909
+};
1910
+
1911
+const HooksDispatcherOnMount: Dispatcher = {
1912
+ readContext,
1913
+
1914
+ useCallback: mountCallback,
1915
+ useContext: readContext,
1916
+ useEffect: mountEffect,
1917
+ useImperativeHandle: mountImperativeHandle,
1918
+ useLayoutEffect: mountLayoutEffect,
1919
+ useMemo: mountMemo,
1920
+ useReducer: mountReducer,
1921
+ useRef: mountRef,
1922
+ useState: mountState,
1923
+ useDebugValue: mountDebugValue,
1924
+ useResponder: createDeprecatedResponderListener,
1925
+ useDeferredValue: mountDeferredValue,
1926
+ useTransition: mountTransition,
1927
+ useMutableSource: mountMutableSource,
1928
+ useEvent: mountEventListener,
1929
+ useOpaqueIdentifier: mountOpaqueIdentifier,
1930
+};
1931
+
1932
+const HooksDispatcherOnUpdate: Dispatcher = {
1933
+ readContext,
1934
+
1935
+ useCallback: updateCallback,
1936
+ useContext: readContext,
1937
+ useEffect: updateEffect,
1938
+ useImperativeHandle: updateImperativeHandle,
1939
+ useLayoutEffect: updateLayoutEffect,
1940
+ useMemo: updateMemo,
1941
+ useReducer: updateReducer,
1942
+ useRef: updateRef,
1943
+ useState: updateState,
1944
+ useDebugValue: updateDebugValue,
1945
+ useResponder: createDeprecatedResponderListener,
1946
+ useDeferredValue: updateDeferredValue,
1947
+ useTransition: updateTransition,
1948
+ useMutableSource: updateMutableSource,
1949
+ useEvent: updateEventListener,
1950
+ useOpaqueIdentifier: updateOpaqueIdentifier,
1951
+};
1952
+
1953
+const HooksDispatcherOnRerender: Dispatcher = {
1954
+ readContext,
1955
+
1956
+ useCallback: updateCallback,
1957
+ useContext: readContext,
1958
+ useEffect: updateEffect,
1959
+ useImperativeHandle: updateImperativeHandle,
1960
+ useLayoutEffect: updateLayoutEffect,
1961
+ useMemo: updateMemo,
1962
+ useReducer: rerenderReducer,
1963
+ useRef: updateRef,
1964
+ useState: rerenderState,
1965
+ useDebugValue: updateDebugValue,
1966
+ useResponder: createDeprecatedResponderListener,
1967
+ useDeferredValue: rerenderDeferredValue,
1968
+ useTransition: rerenderTransition,
1969
+ useMutableSource: updateMutableSource,
1970
+ useEvent: updateEventListener,
1971
+ useOpaqueIdentifier: rerenderOpaqueIdentifier,
1972
+};
1973
+
1974
+let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
1975
+let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
1976
+let HooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
1977
+let HooksDispatcherOnRerenderInDEV: Dispatcher | null = null;
1978
+let InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher | null = null;
1979
+let InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
1980
+let InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher | null = null;
1981
+
1982
+if (__DEV__) {
1983
+ const warnInvalidContextAccess = () => {
1984
+ console.error(
1985
+ 'Context can only be read while React is rendering. ' +
1986
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
1987
+ 'In function components, you can read it directly in the function body, but not ' +
1988
+ 'inside Hooks like useReducer() or useMemo().',
1989
+ );
1990
+ };
1991
+
1992
+ const warnInvalidHookAccess = () => {
1993
+ console.error(
1994
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
1995
+ 'You can only call Hooks at the top level of your React function. ' +
1996
+ 'For more information, see ' +
1997
+ 'https://fb.me/rules-of-hooks',
1998
+ );
1999
+ };
2000
+
2001
+ HooksDispatcherOnMountInDEV = {
2002
+ readContext<T>(
2003
+ context: ReactContext<T>,
2004
+ observedBits: void | number | boolean,
2005
+ ): T {
2006
+ return readContext(context, observedBits);
2007
+ },
2008
+ useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
2009
+ currentHookNameInDev = 'useCallback';
2010
+ mountHookTypesDev();
2011
+ checkDepsAreArrayDev(deps);
2012
+ return mountCallback(callback, deps);
2013
+ },
2014
+ useContext<T>(
2015
+ context: ReactContext<T>,
2016
+ observedBits: void | number | boolean,
2017
+ ): T {
2018
+ currentHookNameInDev = 'useContext';
2019
+ mountHookTypesDev();
2020
+ return readContext(context, observedBits);
2021
+ },
2022
+ useEffect(
2023
+ create: () => (() => void) | void,
2024
+ deps: Array<mixed> | void | null,
2025
+ ): void {
2026
+ currentHookNameInDev = 'useEffect';
2027
+ mountHookTypesDev();
2028
+ checkDepsAreArrayDev(deps);
2029
+ return mountEffect(create, deps);
2030
+ },
2031
+ useImperativeHandle<T>(
2032
+ ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
2033
+ create: () => T,
2034
+ deps: Array<mixed> | void | null,
2035
+ ): void {
2036
+ currentHookNameInDev = 'useImperativeHandle';
2037
+ mountHookTypesDev();
2038
+ checkDepsAreArrayDev(deps);
2039
+ return mountImperativeHandle(ref, create, deps);
2040
+ },
2041
+ useLayoutEffect(
2042
+ create: () => (() => void) | void,
2043
+ deps: Array<mixed> | void | null,
2044
+ ): void {
2045
+ currentHookNameInDev = 'useLayoutEffect';
2046
+ mountHookTypesDev();
2047
+ checkDepsAreArrayDev(deps);
2048
+ return mountLayoutEffect(create, deps);
2049
+ },
2050
+ useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
2051
+ currentHookNameInDev = 'useMemo';
2052
+ mountHookTypesDev();
2053
+ checkDepsAreArrayDev(deps);
2054
+ const prevDispatcher = ReactCurrentDispatcher.current;
2055
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
2056
+ try {
2057
+ return mountMemo(create, deps);
2058
+ } finally {
2059
+ ReactCurrentDispatcher.current = prevDispatcher;
2060
+ }
2061
+ },
2062
+ useReducer<S, I, A>(
2063
+ reducer: (S, A) => S,
2064
+ initialArg: I,
2065
+ init?: I => S,
2066
+ ): [S, Dispatch<A>] {
2067
+ currentHookNameInDev = 'useReducer';
2068
+ mountHookTypesDev();
2069
+ const prevDispatcher = ReactCurrentDispatcher.current;
2070
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
2071
+ try {
2072
+ return mountReducer(reducer, initialArg, init);
2073
+ } finally {
2074
+ ReactCurrentDispatcher.current = prevDispatcher;
2075
+ }
2076
+ },
2077
+ useRef<T>(initialValue: T): {|current: T|} {
2078
+ currentHookNameInDev = 'useRef';
2079
+ mountHookTypesDev();
2080
+ return mountRef(initialValue);
2081
+ },
2082
+ useState<S>(
2083
+ initialState: (() => S) | S,
2084
+ ): [S, Dispatch<BasicStateAction<S>>] {
2085
+ currentHookNameInDev = 'useState';
2086
+ mountHookTypesDev();
2087
+ const prevDispatcher = ReactCurrentDispatcher.current;
2088
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
2089
+ try {
2090
+ return mountState(initialState);
2091
+ } finally {
2092
+ ReactCurrentDispatcher.current = prevDispatcher;
2093
+ }
2094
+ },
2095
+ useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
2096
+ currentHookNameInDev = 'useDebugValue';
2097
+ mountHookTypesDev();
2098
+ return mountDebugValue(value, formatterFn);
2099
+ },
2100
+ useResponder<E, C>(
2101
+ responder: ReactEventResponder<E, C>,
2102
+ props,
2103
+ ): ReactEventResponderListener<E, C> {
2104
+ currentHookNameInDev = 'useResponder';
2105
+ mountHookTypesDev();
2106
+ return createDeprecatedResponderListener(responder, props);
2107
+ },
2108
+ useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
2109
+ currentHookNameInDev = 'useDeferredValue';
2110
+ mountHookTypesDev();
2111
+ return mountDeferredValue(value, config);
2112
+ },
2113
+ useTransition(
2114
+ config: SuspenseConfig | void | null,
2115
+ ): [(() => void) => void, boolean] {
2116
+ currentHookNameInDev = 'useTransition';
2117
+ mountHookTypesDev();
2118
+ return mountTransition(config);
2119
+ },
2120
+ useMutableSource<Source, Snapshot>(
2121
+ source: MutableSource<Source>,
2122
+ getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
2123
+ subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
2124
+ ): Snapshot {
2125
+ currentHookNameInDev = 'useMutableSource';
2126
+ mountHookTypesDev();
2127
+ return mountMutableSource(source, getSnapshot, subscribe);
2128
+ },
2129
+ useEvent(event: ReactListenerEvent): ReactListenerMap {
2130
+ currentHookNameInDev = 'useEvent';
2131
+ mountHookTypesDev();
2132
+ return mountEventListener(event);
2133
+ },
2134
+ useOpaqueIdentifier(): OpaqueIDType | void {
2135
+ currentHookNameInDev = 'useOpaqueIdentifier';
2136
+ mountHookTypesDev();
2137
+ return mountOpaqueIdentifier();
2138
+ },
2139
+ };
2140
+
2141
+ HooksDispatcherOnMountWithHookTypesInDEV = {
2142
+ readContext<T>(
2143
+ context: ReactContext<T>,
2144
+ observedBits: void | number | boolean,
2145
+ ): T {
2146
+ return readContext(context, observedBits);
2147
+ },
2148
+ useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
2149
+ currentHookNameInDev = 'useCallback';
2150
+ updateHookTypesDev();
2151
+ return mountCallback(callback, deps);
2152
+ },
2153
+ useContext<T>(
2154
+ context: ReactContext<T>,
2155
+ observedBits: void | number | boolean,
2156
+ ): T {
2157
+ currentHookNameInDev = 'useContext';
2158
+ updateHookTypesDev();
2159
+ return readContext(context, observedBits);
2160
+ },
2161
+ useEffect(
2162
+ create: () => (() => void) | void,
2163
+ deps: Array<mixed> | void | null,
2164
+ ): void {
2165
+ currentHookNameInDev = 'useEffect';
2166
+ updateHookTypesDev();
2167
+ return mountEffect(create, deps);
2168
+ },
2169
+ useImperativeHandle<T>(
2170
+ ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
2171
+ create: () => T,
2172
+ deps: Array<mixed> | void | null,
2173
+ ): void {
2174
+ currentHookNameInDev = 'useImperativeHandle';
2175
+ updateHookTypesDev();
2176
+ return mountImperativeHandle(ref, create, deps);
2177
+ },
2178
+ useLayoutEffect(
2179
+ create: () => (() => void) | void,
2180
+ deps: Array<mixed> | void | null,
2181
+ ): void {
2182
+ currentHookNameInDev = 'useLayoutEffect';
2183
+ updateHookTypesDev();
2184
+ return mountLayoutEffect(create, deps);
2185
+ },
2186
+ useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
2187
+ currentHookNameInDev = 'useMemo';
2188
+ updateHookTypesDev();
2189
+ const prevDispatcher = ReactCurrentDispatcher.current;
2190
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
2191
+ try {
2192
+ return mountMemo(create, deps);
2193
+ } finally {
2194
+ ReactCurrentDispatcher.current = prevDispatcher;
2195
+ }
2196
+ },
2197
+ useReducer<S, I, A>(
2198
+ reducer: (S, A) => S,
2199
+ initialArg: I,
2200
+ init?: I => S,
2201
+ ): [S, Dispatch<A>] {
2202
+ currentHookNameInDev = 'useReducer';
2203
+ updateHookTypesDev();
2204
+ const prevDispatcher = ReactCurrentDispatcher.current;
2205
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
2206
+ try {
2207
+ return mountReducer(reducer, initialArg, init);
2208
+ } finally {
2209
+ ReactCurrentDispatcher.current = prevDispatcher;
2210
+ }
2211
+ },
2212
+ useRef<T>(initialValue: T): {|current: T|} {
2213
+ currentHookNameInDev = 'useRef';
2214
+ updateHookTypesDev();
2215
+ return mountRef(initialValue);
2216
+ },
2217
+ useState<S>(
2218
+ initialState: (() => S) | S,
2219
+ ): [S, Dispatch<BasicStateAction<S>>] {
2220
+ currentHookNameInDev = 'useState';
2221
+ updateHookTypesDev();
2222
+ const prevDispatcher = ReactCurrentDispatcher.current;
2223
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
2224
+ try {
2225
+ return mountState(initialState);
2226
+ } finally {
2227
+ ReactCurrentDispatcher.current = prevDispatcher;
2228
+ }
2229
+ },
2230
+ useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
2231
+ currentHookNameInDev = 'useDebugValue';
2232
+ updateHookTypesDev();
2233
+ return mountDebugValue(value, formatterFn);
2234
+ },
2235
+ useResponder<E, C>(
2236
+ responder: ReactEventResponder<E, C>,
2237
+ props,
2238
+ ): ReactEventResponderListener<E, C> {
2239
+ currentHookNameInDev = 'useResponder';
2240
+ updateHookTypesDev();
2241
+ return createDeprecatedResponderListener(responder, props);
2242
+ },
2243
+ useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
2244
+ currentHookNameInDev = 'useDeferredValue';
2245
+ updateHookTypesDev();
2246
+ return mountDeferredValue(value, config);
2247
+ },
2248
+ useTransition(
2249
+ config: SuspenseConfig | void | null,
2250
+ ): [(() => void) => void, boolean] {
2251
+ currentHookNameInDev = 'useTransition';
2252
+ updateHookTypesDev();
2253
+ return mountTransition(config);
2254
+ },
2255
+ useMutableSource<Source, Snapshot>(
2256
+ source: MutableSource<Source>,
2257
+ getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
2258
+ subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
2259
+ ): Snapshot {
2260
+ currentHookNameInDev = 'useMutableSource';
2261
+ updateHookTypesDev();
2262
+ return mountMutableSource(source, getSnapshot, subscribe);
2263
+ },
2264
+ useEvent(event: ReactListenerEvent): ReactListenerMap {
2265
+ currentHookNameInDev = 'useEvent';
2266
+ updateHookTypesDev();
2267
+ return mountEventListener(event);
2268
+ },
2269
+ useOpaqueIdentifier(): OpaqueIDType | void {
2270
+ currentHookNameInDev = 'useOpaqueIdentifier';
2271
+ updateHookTypesDev();
2272
+ return mountOpaqueIdentifier();
2273
+ },
2274
+ };
2275
+
2276
+ HooksDispatcherOnUpdateInDEV = {
2277
+ readContext<T>(
2278
+ context: ReactContext<T>,
2279
+ observedBits: void | number | boolean,
2280
+ ): T {
2281
+ return readContext(context, observedBits);
2282
+ },
2283
+ useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
2284
+ currentHookNameInDev = 'useCallback';
2285
+ updateHookTypesDev();
2286
+ return updateCallback(callback, deps);
2287
+ },
2288
+ useContext<T>(
2289
+ context: ReactContext<T>,
2290
+ observedBits: void | number | boolean,
2291
+ ): T {
2292
+ currentHookNameInDev = 'useContext';
2293
+ updateHookTypesDev();
2294
+ return readContext(context, observedBits);
2295
+ },
2296
+ useEffect(
2297
+ create: () => (() => void) | void,
2298
+ deps: Array<mixed> | void | null,
2299
+ ): void {
2300
+ currentHookNameInDev = 'useEffect';
2301
+ updateHookTypesDev();
2302
+ return updateEffect(create, deps);
2303
+ },
2304
+ useImperativeHandle<T>(
2305
+ ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
2306
+ create: () => T,
2307
+ deps: Array<mixed> | void | null,
2308
+ ): void {
2309
+ currentHookNameInDev = 'useImperativeHandle';
2310
+ updateHookTypesDev();
2311
+ return updateImperativeHandle(ref, create, deps);
2312
+ },
2313
+ useLayoutEffect(
2314
+ create: () => (() => void) | void,
2315
+ deps: Array<mixed> | void | null,
2316
+ ): void {
2317
+ currentHookNameInDev = 'useLayoutEffect';
2318
+ updateHookTypesDev();
2319
+ return updateLayoutEffect(create, deps);
2320
+ },
2321
+ useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
2322
+ currentHookNameInDev = 'useMemo';
2323
+ updateHookTypesDev();
2324
+ const prevDispatcher = ReactCurrentDispatcher.current;
2325
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
2326
+ try {
2327
+ return updateMemo(create, deps);
2328
+ } finally {
2329
+ ReactCurrentDispatcher.current = prevDispatcher;
2330
+ }
2331
+ },
2332
+ useReducer<S, I, A>(
2333
+ reducer: (S, A) => S,
2334
+ initialArg: I,
2335
+ init?: I => S,
2336
+ ): [S, Dispatch<A>] {
2337
+ currentHookNameInDev = 'useReducer';
2338
+ updateHookTypesDev();
2339
+ const prevDispatcher = ReactCurrentDispatcher.current;
2340
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
2341
+ try {
2342
+ return updateReducer(reducer, initialArg, init);
2343
+ } finally {
2344
+ ReactCurrentDispatcher.current = prevDispatcher;
2345
+ }
2346
+ },
2347
+ useRef<T>(initialValue: T): {|current: T|} {
2348
+ currentHookNameInDev = 'useRef';
2349
+ updateHookTypesDev();
2350
+ return updateRef(initialValue);
2351
+ },
2352
+ useState<S>(
2353
+ initialState: (() => S) | S,
2354
+ ): [S, Dispatch<BasicStateAction<S>>] {
2355
+ currentHookNameInDev = 'useState';
2356
+ updateHookTypesDev();
2357
+ const prevDispatcher = ReactCurrentDispatcher.current;
2358
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
2359
+ try {
2360
+ return updateState(initialState);
2361
+ } finally {
2362
+ ReactCurrentDispatcher.current = prevDispatcher;
2363
+ }
2364
+ },
2365
+ useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
2366
+ currentHookNameInDev = 'useDebugValue';
2367
+ updateHookTypesDev();
2368
+ return updateDebugValue(value, formatterFn);
2369
+ },
2370
+ useResponder<E, C>(
2371
+ responder: ReactEventResponder<E, C>,
2372
+ props,
2373
+ ): ReactEventResponderListener<E, C> {
2374
+ currentHookNameInDev = 'useResponder';
2375
+ updateHookTypesDev();
2376
+ return createDeprecatedResponderListener(responder, props);
2377
+ },
2378
+ useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
2379
+ currentHookNameInDev = 'useDeferredValue';
2380
+ updateHookTypesDev();
2381
+ return updateDeferredValue(value, config);
2382
+ },
2383
+ useTransition(
2384
+ config: SuspenseConfig | void | null,
2385
+ ): [(() => void) => void, boolean] {
2386
+ currentHookNameInDev = 'useTransition';
2387
+ updateHookTypesDev();
2388
+ return updateTransition(config);
2389
+ },
2390
+ useMutableSource<Source, Snapshot>(
2391
+ source: MutableSource<Source>,
2392
+ getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
2393
+ subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
2394
+ ): Snapshot {
2395
+ currentHookNameInDev = 'useMutableSource';
2396
+ updateHookTypesDev();
2397
+ return updateMutableSource(source, getSnapshot, subscribe);
2398
+ },
2399
+ useEvent(event: ReactListenerEvent): ReactListenerMap {
2400
+ currentHookNameInDev = 'useEvent';
2401
+ updateHookTypesDev();
2402
+ return updateEventListener(event);
2403
+ },
2404
+ useOpaqueIdentifier(): OpaqueIDType | void {
2405
+ currentHookNameInDev = 'useOpaqueIdentifier';
2406
+ updateHookTypesDev();
2407
+ return updateOpaqueIdentifier();
2408
+ },
2409
+ };
2410
+
2411
+ HooksDispatcherOnRerenderInDEV = {
2412
+ readContext<T>(
2413
+ context: ReactContext<T>,
2414
+ observedBits: void | number | boolean,
2415
+ ): T {
2416
+ return readContext(context, observedBits);
2417
+ },
2418
+
2419
+ useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
2420
+ currentHookNameInDev = 'useCallback';
2421
+ updateHookTypesDev();
2422
+ return updateCallback(callback, deps);
2423
+ },
2424
+ useContext<T>(
2425
+ context: ReactContext<T>,
2426
+ observedBits: void | number | boolean,
2427
+ ): T {
2428
+ currentHookNameInDev = 'useContext';
2429
+ updateHookTypesDev();
2430
+ return readContext(context, observedBits);
2431
+ },
2432
+ useEffect(
2433
+ create: () => (() => void) | void,
2434
+ deps: Array<mixed> | void | null,
2435
+ ): void {
2436
+ currentHookNameInDev = 'useEffect';
2437
+ updateHookTypesDev();
2438
+ return updateEffect(create, deps);
2439
+ },
2440
+ useImperativeHandle<T>(
2441
+ ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
2442
+ create: () => T,
2443
+ deps: Array<mixed> | void | null,
2444
+ ): void {
2445
+ currentHookNameInDev = 'useImperativeHandle';
2446
+ updateHookTypesDev();
2447
+ return updateImperativeHandle(ref, create, deps);
2448
+ },
2449
+ useLayoutEffect(
2450
+ create: () => (() => void) | void,
2451
+ deps: Array<mixed> | void | null,
2452
+ ): void {
2453
+ currentHookNameInDev = 'useLayoutEffect';
2454
+ updateHookTypesDev();
2455
+ return updateLayoutEffect(create, deps);
2456
+ },
2457
+ useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
2458
+ currentHookNameInDev = 'useMemo';
2459
+ updateHookTypesDev();
2460
+ const prevDispatcher = ReactCurrentDispatcher.current;
2461
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
2462
+ try {
2463
+ return updateMemo(create, deps);
2464
+ } finally {
2465
+ ReactCurrentDispatcher.current = prevDispatcher;
2466
+ }
2467
+ },
2468
+ useReducer<S, I, A>(
2469
+ reducer: (S, A) => S,
2470
+ initialArg: I,
2471
+ init?: I => S,
2472
+ ): [S, Dispatch<A>] {
2473
+ currentHookNameInDev = 'useReducer';
2474
+ updateHookTypesDev();
2475
+ const prevDispatcher = ReactCurrentDispatcher.current;
2476
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
2477
+ try {
2478
+ return rerenderReducer(reducer, initialArg, init);
2479
+ } finally {
2480
+ ReactCurrentDispatcher.current = prevDispatcher;
2481
+ }
2482
+ },
2483
+ useRef<T>(initialValue: T): {|current: T|} {
2484
+ currentHookNameInDev = 'useRef';
2485
+ updateHookTypesDev();
2486
+ return updateRef(initialValue);
2487
+ },
2488
+ useState<S>(
2489
+ initialState: (() => S) | S,
2490
+ ): [S, Dispatch<BasicStateAction<S>>] {
2491
+ currentHookNameInDev = 'useState';
2492
+ updateHookTypesDev();
2493
+ const prevDispatcher = ReactCurrentDispatcher.current;
2494
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
2495
+ try {
2496
+ return rerenderState(initialState);
2497
+ } finally {
2498
+ ReactCurrentDispatcher.current = prevDispatcher;
2499
+ }
2500
+ },
2501
+ useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
2502
+ currentHookNameInDev = 'useDebugValue';
2503
+ updateHookTypesDev();
2504
+ return updateDebugValue(value, formatterFn);
2505
+ },
2506
+ useResponder<E, C>(
2507
+ responder: ReactEventResponder<E, C>,
2508
+ props,
2509
+ ): ReactEventResponderListener<E, C> {
2510
+ currentHookNameInDev = 'useResponder';
2511
+ updateHookTypesDev();
2512
+ return createDeprecatedResponderListener(responder, props);
2513
+ },
2514
+ useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
2515
+ currentHookNameInDev = 'useDeferredValue';
2516
+ updateHookTypesDev();
2517
+ return rerenderDeferredValue(value, config);
2518
+ },
2519
+ useTransition(
2520
+ config: SuspenseConfig | void | null,
2521
+ ): [(() => void) => void, boolean] {
2522
+ currentHookNameInDev = 'useTransition';
2523
+ updateHookTypesDev();
2524
+ return rerenderTransition(config);
2525
+ },
2526
+ useMutableSource<Source, Snapshot>(
2527
+ source: MutableSource<Source>,
2528
+ getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
2529
+ subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
2530
+ ): Snapshot {
2531
+ currentHookNameInDev = 'useMutableSource';
2532
+ updateHookTypesDev();
2533
+ return updateMutableSource(source, getSnapshot, subscribe);
2534
+ },
2535
+ useEvent(event: ReactListenerEvent): ReactListenerMap {
2536
+ currentHookNameInDev = 'useEvent';
2537
+ updateHookTypesDev();
2538
+ return updateEventListener(event);
2539
+ },
2540
+ useOpaqueIdentifier(): OpaqueIDType | void {
2541
+ currentHookNameInDev = 'useOpaqueIdentifier';
2542
+ updateHookTypesDev();
2543
+ return rerenderOpaqueIdentifier();
2544
+ },
2545
+ };
2546
+
2547
+ InvalidNestedHooksDispatcherOnMountInDEV = {
2548
+ readContext<T>(
2549
+ context: ReactContext<T>,
2550
+ observedBits: void | number | boolean,
2551
+ ): T {
2552
+ warnInvalidContextAccess();
2553
+ return readContext(context, observedBits);
2554
+ },
2555
+ useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
2556
+ currentHookNameInDev = 'useCallback';
2557
+ warnInvalidHookAccess();
2558
+ mountHookTypesDev();
2559
+ return mountCallback(callback, deps);
2560
+ },
2561
+ useContext<T>(
2562
+ context: ReactContext<T>,
2563
+ observedBits: void | number | boolean,
2564
+ ): T {
2565
+ currentHookNameInDev = 'useContext';
2566
+ warnInvalidHookAccess();
2567
+ mountHookTypesDev();
2568
+ return readContext(context, observedBits);
2569
+ },
2570
+ useEffect(
2571
+ create: () => (() => void) | void,
2572
+ deps: Array<mixed> | void | null,
2573
+ ): void {
2574
+ currentHookNameInDev = 'useEffect';
2575
+ warnInvalidHookAccess();
2576
+ mountHookTypesDev();
2577
+ return mountEffect(create, deps);
2578
+ },
2579
+ useImperativeHandle<T>(
2580
+ ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
2581
+ create: () => T,
2582
+ deps: Array<mixed> | void | null,
2583
+ ): void {
2584
+ currentHookNameInDev = 'useImperativeHandle';
2585
+ warnInvalidHookAccess();
2586
+ mountHookTypesDev();
2587
+ return mountImperativeHandle(ref, create, deps);
2588
+ },
2589
+ useLayoutEffect(
2590
+ create: () => (() => void) | void,
2591
+ deps: Array<mixed> | void | null,
2592
+ ): void {
2593
+ currentHookNameInDev = 'useLayoutEffect';
2594
+ warnInvalidHookAccess();
2595
+ mountHookTypesDev();
2596
+ return mountLayoutEffect(create, deps);
2597
+ },
2598
+ useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
2599
+ currentHookNameInDev = 'useMemo';
2600
+ warnInvalidHookAccess();
2601
+ mountHookTypesDev();
2602
+ const prevDispatcher = ReactCurrentDispatcher.current;
2603
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
2604
+ try {
2605
+ return mountMemo(create, deps);
2606
+ } finally {
2607
+ ReactCurrentDispatcher.current = prevDispatcher;
2608
+ }
2609
+ },
2610
+ useReducer<S, I, A>(
2611
+ reducer: (S, A) => S,
2612
+ initialArg: I,
2613
+ init?: I => S,
2614
+ ): [S, Dispatch<A>] {
2615
+ currentHookNameInDev = 'useReducer';
2616
+ warnInvalidHookAccess();
2617
+ mountHookTypesDev();
2618
+ const prevDispatcher = ReactCurrentDispatcher.current;
2619
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
2620
+ try {
2621
+ return mountReducer(reducer, initialArg, init);
2622
+ } finally {
2623
+ ReactCurrentDispatcher.current = prevDispatcher;
2624
+ }
2625
+ },
2626
+ useRef<T>(initialValue: T): {|current: T|} {
2627
+ currentHookNameInDev = 'useRef';
2628
+ warnInvalidHookAccess();
2629
+ mountHookTypesDev();
2630
+ return mountRef(initialValue);
2631
+ },
2632
+ useState<S>(
2633
+ initialState: (() => S) | S,
2634
+ ): [S, Dispatch<BasicStateAction<S>>] {
2635
+ currentHookNameInDev = 'useState';
2636
+ warnInvalidHookAccess();
2637
+ mountHookTypesDev();
2638
+ const prevDispatcher = ReactCurrentDispatcher.current;
2639
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
2640
+ try {
2641
+ return mountState(initialState);
2642
+ } finally {
2643
+ ReactCurrentDispatcher.current = prevDispatcher;
2644
+ }
2645
+ },
2646
+ useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
2647
+ currentHookNameInDev = 'useDebugValue';
2648
+ warnInvalidHookAccess();
2649
+ mountHookTypesDev();
2650
+ return mountDebugValue(value, formatterFn);
2651
+ },
2652
+ useResponder<E, C>(
2653
+ responder: ReactEventResponder<E, C>,
2654
+ props,
2655
+ ): ReactEventResponderListener<E, C> {
2656
+ currentHookNameInDev = 'useResponder';
2657
+ warnInvalidHookAccess();
2658
+ mountHookTypesDev();
2659
+ return createDeprecatedResponderListener(responder, props);
2660
+ },
2661
+ useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
2662
+ currentHookNameInDev = 'useDeferredValue';
2663
+ warnInvalidHookAccess();
2664
+ mountHookTypesDev();
2665
+ return mountDeferredValue(value, config);
2666
+ },
2667
+ useTransition(
2668
+ config: SuspenseConfig | void | null,
2669
+ ): [(() => void) => void, boolean] {
2670
+ currentHookNameInDev = 'useTransition';
2671
+ warnInvalidHookAccess();
2672
+ mountHookTypesDev();
2673
+ return mountTransition(config);
2674
+ },
2675
+ useMutableSource<Source, Snapshot>(
2676
+ source: MutableSource<Source>,
2677
+ getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
2678
+ subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
2679
+ ): Snapshot {
2680
+ currentHookNameInDev = 'useMutableSource';
2681
+ warnInvalidHookAccess();
2682
+ mountHookTypesDev();
2683
+ return mountMutableSource(source, getSnapshot, subscribe);
2684
+ },
2685
+ useEvent(event: ReactListenerEvent): ReactListenerMap {
2686
+ currentHookNameInDev = 'useEvent';
2687
+ warnInvalidHookAccess();
2688
+ mountHookTypesDev();
2689
+ return mountEventListener(event);
2690
+ },
2691
+ useOpaqueIdentifier(): OpaqueIDType | void {
2692
+ currentHookNameInDev = 'useOpaqueIdentifier';
2693
+ warnInvalidHookAccess();
2694
+ mountHookTypesDev();
2695
+ return mountOpaqueIdentifier();
2696
+ },
2697
+ };
2698
+
2699
+ InvalidNestedHooksDispatcherOnUpdateInDEV = {
2700
+ readContext<T>(
2701
+ context: ReactContext<T>,
2702
+ observedBits: void | number | boolean,
2703
+ ): T {
2704
+ warnInvalidContextAccess();
2705
+ return readContext(context, observedBits);
2706
+ },
2707
+ useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
2708
+ currentHookNameInDev = 'useCallback';
2709
+ warnInvalidHookAccess();
2710
+ updateHookTypesDev();
2711
+ return updateCallback(callback, deps);
2712
+ },
2713
+ useContext<T>(
2714
+ context: ReactContext<T>,
2715
+ observedBits: void | number | boolean,
2716
+ ): T {
2717
+ currentHookNameInDev = 'useContext';
2718
+ warnInvalidHookAccess();
2719
+ updateHookTypesDev();
2720
+ return readContext(context, observedBits);
2721
+ },
2722
+ useEffect(
2723
+ create: () => (() => void) | void,
2724
+ deps: Array<mixed> | void | null,
2725
+ ): void {
2726
+ currentHookNameInDev = 'useEffect';
2727
+ warnInvalidHookAccess();
2728
+ updateHookTypesDev();
2729
+ return updateEffect(create, deps);
2730
+ },
2731
+ useImperativeHandle<T>(
2732
+ ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
2733
+ create: () => T,
2734
+ deps: Array<mixed> | void | null,
2735
+ ): void {
2736
+ currentHookNameInDev = 'useImperativeHandle';
2737
+ warnInvalidHookAccess();
2738
+ updateHookTypesDev();
2739
+ return updateImperativeHandle(ref, create, deps);
2740
+ },
2741
+ useLayoutEffect(
2742
+ create: () => (() => void) | void,
2743
+ deps: Array<mixed> | void | null,
2744
+ ): void {
2745
+ currentHookNameInDev = 'useLayoutEffect';
2746
+ warnInvalidHookAccess();
2747
+ updateHookTypesDev();
2748
+ return updateLayoutEffect(create, deps);
2749
+ },
2750
+ useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
2751
+ currentHookNameInDev = 'useMemo';
2752
+ warnInvalidHookAccess();
2753
+ updateHookTypesDev();
2754
+ const prevDispatcher = ReactCurrentDispatcher.current;
2755
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
2756
+ try {
2757
+ return updateMemo(create, deps);
2758
+ } finally {
2759
+ ReactCurrentDispatcher.current = prevDispatcher;
2760
+ }
2761
+ },
2762
+ useReducer<S, I, A>(
2763
+ reducer: (S, A) => S,
2764
+ initialArg: I,
2765
+ init?: I => S,
2766
+ ): [S, Dispatch<A>] {
2767
+ currentHookNameInDev = 'useReducer';
2768
+ warnInvalidHookAccess();
2769
+ updateHookTypesDev();
2770
+ const prevDispatcher = ReactCurrentDispatcher.current;
2771
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
2772
+ try {
2773
+ return updateReducer(reducer, initialArg, init);
2774
+ } finally {
2775
+ ReactCurrentDispatcher.current = prevDispatcher;
2776
+ }
2777
+ },
2778
+ useRef<T>(initialValue: T): {|current: T|} {
2779
+ currentHookNameInDev = 'useRef';
2780
+ warnInvalidHookAccess();
2781
+ updateHookTypesDev();
2782
+ return updateRef(initialValue);
2783
+ },
2784
+ useState<S>(
2785
+ initialState: (() => S) | S,
2786
+ ): [S, Dispatch<BasicStateAction<S>>] {
2787
+ currentHookNameInDev = 'useState';
2788
+ warnInvalidHookAccess();
2789
+ updateHookTypesDev();
2790
+ const prevDispatcher = ReactCurrentDispatcher.current;
2791
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
2792
+ try {
2793
+ return updateState(initialState);
2794
+ } finally {
2795
+ ReactCurrentDispatcher.current = prevDispatcher;
2796
+ }
2797
+ },
2798
+ useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
2799
+ currentHookNameInDev = 'useDebugValue';
2800
+ warnInvalidHookAccess();
2801
+ updateHookTypesDev();
2802
+ return updateDebugValue(value, formatterFn);
2803
+ },
2804
+ useResponder<E, C>(
2805
+ responder: ReactEventResponder<E, C>,
2806
+ props,
2807
+ ): ReactEventResponderListener<E, C> {
2808
+ currentHookNameInDev = 'useResponder';
2809
+ warnInvalidHookAccess();
2810
+ updateHookTypesDev();
2811
+ return createDeprecatedResponderListener(responder, props);
2812
+ },
2813
+ useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
2814
+ currentHookNameInDev = 'useDeferredValue';
2815
+ warnInvalidHookAccess();
2816
+ updateHookTypesDev();
2817
+ return updateDeferredValue(value, config);
2818
+ },
2819
+ useTransition(
2820
+ config: SuspenseConfig | void | null,
2821
+ ): [(() => void) => void, boolean] {
2822
+ currentHookNameInDev = 'useTransition';
2823
+ warnInvalidHookAccess();
2824
+ updateHookTypesDev();
2825
+ return updateTransition(config);
2826
+ },
2827
+ useMutableSource<Source, Snapshot>(
2828
+ source: MutableSource<Source>,
2829
+ getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
2830
+ subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
2831
+ ): Snapshot {
2832
+ currentHookNameInDev = 'useMutableSource';
2833
+ warnInvalidHookAccess();
2834
+ updateHookTypesDev();
2835
+ return updateMutableSource(source, getSnapshot, subscribe);
2836
+ },
2837
+ useEvent(event: ReactListenerEvent): ReactListenerMap {
2838
+ currentHookNameInDev = 'useEvent';
2839
+ warnInvalidHookAccess();
2840
+ updateHookTypesDev();
2841
+ return updateEventListener(event);
2842
+ },
2843
+ useOpaqueIdentifier(): OpaqueIDType | void {
2844
+ currentHookNameInDev = 'useOpaqueIdentifier';
2845
+ warnInvalidHookAccess();
2846
+ updateHookTypesDev();
2847
+ return updateOpaqueIdentifier();
2848
+ },
2849
+ };
2850
+
2851
+ InvalidNestedHooksDispatcherOnRerenderInDEV = {
2852
+ readContext<T>(
2853
+ context: ReactContext<T>,
2854
+ observedBits: void | number | boolean,
2855
+ ): T {
2856
+ warnInvalidContextAccess();
2857
+ return readContext(context, observedBits);
2858
+ },
2859
+
2860
+ useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
2861
+ currentHookNameInDev = 'useCallback';
2862
+ warnInvalidHookAccess();
2863
+ updateHookTypesDev();
2864
+ return updateCallback(callback, deps);
2865
+ },
2866
+ useContext<T>(
2867
+ context: ReactContext<T>,
2868
+ observedBits: void | number | boolean,
2869
+ ): T {
2870
+ currentHookNameInDev = 'useContext';
2871
+ warnInvalidHookAccess();
2872
+ updateHookTypesDev();
2873
+ return readContext(context, observedBits);
2874
+ },
2875
+ useEffect(
2876
+ create: () => (() => void) | void,
2877
+ deps: Array<mixed> | void | null,
2878
+ ): void {
2879
+ currentHookNameInDev = 'useEffect';
2880
+ warnInvalidHookAccess();
2881
+ updateHookTypesDev();
2882
+ return updateEffect(create, deps);
2883
+ },
2884
+ useImperativeHandle<T>(
2885
+ ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
2886
+ create: () => T,
2887
+ deps: Array<mixed> | void | null,
2888
+ ): void {
2889
+ currentHookNameInDev = 'useImperativeHandle';
2890
+ warnInvalidHookAccess();
2891
+ updateHookTypesDev();
2892
+ return updateImperativeHandle(ref, create, deps);
2893
+ },
2894
+ useLayoutEffect(
2895
+ create: () => (() => void) | void,
2896
+ deps: Array<mixed> | void | null,
2897
+ ): void {
2898
+ currentHookNameInDev = 'useLayoutEffect';
2899
+ warnInvalidHookAccess();
2900
+ updateHookTypesDev();
2901
+ return updateLayoutEffect(create, deps);
2902
+ },
2903
+ useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
2904
+ currentHookNameInDev = 'useMemo';
2905
+ warnInvalidHookAccess();
2906
+ updateHookTypesDev();
2907
+ const prevDispatcher = ReactCurrentDispatcher.current;
2908
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
2909
+ try {
2910
+ return updateMemo(create, deps);
2911
+ } finally {
2912
+ ReactCurrentDispatcher.current = prevDispatcher;
2913
+ }
2914
+ },
2915
+ useReducer<S, I, A>(
2916
+ reducer: (S, A) => S,
2917
+ initialArg: I,
2918
+ init?: I => S,
2919
+ ): [S, Dispatch<A>] {
2920
+ currentHookNameInDev = 'useReducer';
2921
+ warnInvalidHookAccess();
2922
+ updateHookTypesDev();
2923
+ const prevDispatcher = ReactCurrentDispatcher.current;
2924
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
2925
+ try {
2926
+ return rerenderReducer(reducer, initialArg, init);
2927
+ } finally {
2928
+ ReactCurrentDispatcher.current = prevDispatcher;
2929
+ }
2930
+ },
2931
+ useRef<T>(initialValue: T): {|current: T|} {
2932
+ currentHookNameInDev = 'useRef';
2933
+ warnInvalidHookAccess();
2934
+ updateHookTypesDev();
2935
+ return updateRef(initialValue);
2936
+ },
2937
+ useState<S>(
2938
+ initialState: (() => S) | S,
2939
+ ): [S, Dispatch<BasicStateAction<S>>] {
2940
+ currentHookNameInDev = 'useState';
2941
+ warnInvalidHookAccess();
2942
+ updateHookTypesDev();
2943
+ const prevDispatcher = ReactCurrentDispatcher.current;
2944
+ ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
2945
+ try {
2946
+ return rerenderState(initialState);
2947
+ } finally {
2948
+ ReactCurrentDispatcher.current = prevDispatcher;
2949
+ }
2950
+ },
2951
+ useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
2952
+ currentHookNameInDev = 'useDebugValue';
2953
+ warnInvalidHookAccess();
2954
+ updateHookTypesDev();
2955
+ return updateDebugValue(value, formatterFn);
2956
+ },
2957
+ useResponder<E, C>(
2958
+ responder: ReactEventResponder<E, C>,
2959
+ props,
2960
+ ): ReactEventResponderListener<E, C> {
2961
+ currentHookNameInDev = 'useResponder';
2962
+ warnInvalidHookAccess();
2963
+ updateHookTypesDev();
2964
+ return createDeprecatedResponderListener(responder, props);
2965
+ },
2966
+ useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
2967
+ currentHookNameInDev = 'useDeferredValue';
2968
+ warnInvalidHookAccess();
2969
+ updateHookTypesDev();
2970
+ return rerenderDeferredValue(value, config);
2971
+ },
2972
+ useTransition(
2973
+ config: SuspenseConfig | void | null,
2974
+ ): [(() => void) => void, boolean] {
2975
+ currentHookNameInDev = 'useTransition';
2976
+ warnInvalidHookAccess();
2977
+ updateHookTypesDev();
2978
+ return rerenderTransition(config);
2979
+ },
2980
+ useMutableSource<Source, Snapshot>(
2981
+ source: MutableSource<Source>,
2982
+ getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
2983
+ subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
2984
+ ): Snapshot {
2985
+ currentHookNameInDev = 'useMutableSource';
2986
+ warnInvalidHookAccess();
2987
+ updateHookTypesDev();
2988
+ return updateMutableSource(source, getSnapshot, subscribe);
2989
+ },
2990
+ useEvent(event: ReactListenerEvent): ReactListenerMap {
2991
+ currentHookNameInDev = 'useEvent';
2992
+ warnInvalidHookAccess();
2993
+ updateHookTypesDev();
2994
+ return updateEventListener(event);
2995
+ },
2996
+ useOpaqueIdentifier(): OpaqueIDType | void {
2997
+ currentHookNameInDev = 'useOpaqueIdentifier';
2998
+ warnInvalidHookAccess();
2999
+ updateHookTypesDev();
3000
+ return rerenderOpaqueIdentifier();
3001
+ },
3002
+ };
3003
+}
packages/react-reconciler/src/ReactFiberHostContext.new.js
new
+113
@@ -0,0 +1,113 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactInternalTypes';
11
+import type {StackCursor} from './ReactFiberStack.new';
12
+import type {Container, HostContext} from './ReactFiberHostConfig';
13
+
14
+import invariant from 'shared/invariant';
15
+
16
+import {getChildHostContext, getRootHostContext} from './ReactFiberHostConfig';
17
+import {createCursor, push, pop} from './ReactFiberStack.new';
18
+
19
+declare class NoContextT {}
20
+const NO_CONTEXT: NoContextT = ({}: any);
21
+
22
+const contextStackCursor: StackCursor<HostContext | NoContextT> = createCursor(
23
+ NO_CONTEXT,
24
+);
25
+const contextFiberStackCursor: StackCursor<Fiber | NoContextT> = createCursor(
26
+ NO_CONTEXT,
27
+);
28
+const rootInstanceStackCursor: StackCursor<
29
+ Container | NoContextT,
30
+> = createCursor(NO_CONTEXT);
31
+
32
+function requiredContext<Value>(c: Value | NoContextT): Value {
33
+ invariant(
34
+ c !== NO_CONTEXT,
35
+ 'Expected host context to exist. This error is likely caused by a bug ' +
36
+ 'in React. Please file an issue.',
37
+ );
38
+ return (c: any);
39
+}
40
+
41
+function getRootHostContainer(): Container {
42
+ const rootInstance = requiredContext(rootInstanceStackCursor.current);
43
+ return rootInstance;
44
+}
45
+
46
+function pushHostContainer(fiber: Fiber, nextRootInstance: Container) {
47
+ // Push current root instance onto the stack;
48
+ // This allows us to reset root when portals are popped.
49
+ push(rootInstanceStackCursor, nextRootInstance, fiber);
50
+ // Track the context and the Fiber that provided it.
51
+ // This enables us to pop only Fibers that provide unique contexts.
52
+ push(contextFiberStackCursor, fiber, fiber);
53
+
54
+ // Finally, we need to push the host context to the stack.
55
+ // However, we can't just call getRootHostContext() and push it because
56
+ // we'd have a different number of entries on the stack depending on
57
+ // whether getRootHostContext() throws somewhere in renderer code or not.
58
+ // So we push an empty value first. This lets us safely unwind on errors.
59
+ push(contextStackCursor, NO_CONTEXT, fiber);
60
+ const nextRootContext = getRootHostContext(nextRootInstance);
61
+ // Now that we know this function doesn't throw, replace it.
62
+ pop(contextStackCursor, fiber);
63
+ push(contextStackCursor, nextRootContext, fiber);
64
+}
65
+
66
+function popHostContainer(fiber: Fiber) {
67
+ pop(contextStackCursor, fiber);
68
+ pop(contextFiberStackCursor, fiber);
69
+ pop(rootInstanceStackCursor, fiber);
70
+}
71
+
72
+function getHostContext(): HostContext {
73
+ const context = requiredContext(contextStackCursor.current);
74
+ return context;
75
+}
76
+
77
+function pushHostContext(fiber: Fiber): void {
78
+ const rootInstance: Container = requiredContext(
79
+ rootInstanceStackCursor.current,
80
+ );
81
+ const context: HostContext = requiredContext(contextStackCursor.current);
82
+ const nextContext = getChildHostContext(context, fiber.type, rootInstance);
83
+
84
+ // Don't push this Fiber's context unless it's unique.
85
+ if (context === nextContext) {
86
+ return;
87
+ }
88
+
89
+ // Track the context and the Fiber that provided it.
90
+ // This enables us to pop only Fibers that provide unique contexts.
91
+ push(contextFiberStackCursor, fiber, fiber);
92
+ push(contextStackCursor, nextContext, fiber);
93
+}
94
+
95
+function popHostContext(fiber: Fiber): void {
96
+ // Do not pop unless this Fiber provided the current context.
97
+ // pushHostContext() only pushes Fibers that provide unique contexts.
98
+ if (contextFiberStackCursor.current !== fiber) {
99
+ return;
100
+ }
101
+
102
+ pop(contextStackCursor, fiber);
103
+ pop(contextFiberStackCursor, fiber);
104
+}
105
+
106
+export {
107
+ getHostContext,
108
+ getRootHostContainer,
109
+ popHostContainer,
110
+ popHostContext,
111
+ pushHostContainer,
112
+ pushHostContext,
113
+};
packages/react-reconciler/src/ReactFiberHotReloading.new.js
new
+482
@@ -0,0 +1,482 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {ReactElement} from 'shared/ReactElementType';
11
+import type {Fiber} from './ReactInternalTypes';
12
+import type {FiberRoot} from './ReactInternalTypes';
13
+import type {Instance} from './ReactFiberHostConfig';
14
+import type {ReactNodeList} from 'shared/ReactTypes';
15
+
16
+import {
17
+ flushSync,
18
+ scheduleUpdateOnFiber,
19
+ flushPassiveEffects,
20
+} from './ReactFiberWorkLoop.new';
21
+import {updateContainer, syncUpdates} from './ReactFiberReconciler.new';
22
+import {emptyContextObject} from './ReactFiberContext.new';
23
+import {Sync} from './ReactFiberExpirationTime';
24
+import {
25
+ ClassComponent,
26
+ FunctionComponent,
27
+ ForwardRef,
28
+ HostComponent,
29
+ HostPortal,
30
+ HostRoot,
31
+ MemoComponent,
32
+ SimpleMemoComponent,
33
+} from './ReactWorkTags';
34
+import {
35
+ REACT_FORWARD_REF_TYPE,
36
+ REACT_MEMO_TYPE,
37
+ REACT_LAZY_TYPE,
38
+} from 'shared/ReactSymbols';
39
+
40
+export type Family = {|
41
+ current: any,
42
+|};
43
+
44
+export type RefreshUpdate = {|
45
+ staleFamilies: Set<Family>,
46
+ updatedFamilies: Set<Family>,
47
+|};
48
+
49
+// Resolves type to a family.
50
+type RefreshHandler = any => Family | void;
51
+
52
+// Used by React Refresh runtime through DevTools Global Hook.
53
+export type SetRefreshHandler = (handler: RefreshHandler | null) => void;
54
+export type ScheduleRefresh = (root: FiberRoot, update: RefreshUpdate) => void;
55
+export type ScheduleRoot = (root: FiberRoot, element: ReactNodeList) => void;
56
+export type FindHostInstancesForRefresh = (
57
+ root: FiberRoot,
58
+ families: Array<Family>,
59
+) => Set<Instance>;
60
+
61
+let resolveFamily: RefreshHandler | null = null;
62
+// $FlowFixMe Flow gets confused by a WeakSet feature check below.
63
+let failedBoundaries: WeakSet<Fiber> | null = null;
64
+
65
+export const setRefreshHandler = (handler: RefreshHandler | null): void => {
66
+ if (__DEV__) {
67
+ resolveFamily = handler;
68
+ }
69
+};
70
+
71
+export function resolveFunctionForHotReloading(type: any): any {
72
+ if (__DEV__) {
73
+ if (resolveFamily === null) {
74
+ // Hot reloading is disabled.
75
+ return type;
76
+ }
77
+ const family = resolveFamily(type);
78
+ if (family === undefined) {
79
+ return type;
80
+ }
81
+ // Use the latest known implementation.
82
+ return family.current;
83
+ } else {
84
+ return type;
85
+ }
86
+}
87
+
88
+export function resolveClassForHotReloading(type: any): any {
89
+ // No implementation differences.
90
+ return resolveFunctionForHotReloading(type);
91
+}
92
+
93
+export function resolveForwardRefForHotReloading(type: any): any {
94
+ if (__DEV__) {
95
+ if (resolveFamily === null) {
96
+ // Hot reloading is disabled.
97
+ return type;
98
+ }
99
+ const family = resolveFamily(type);
100
+ if (family === undefined) {
101
+ // Check if we're dealing with a real forwardRef. Don't want to crash early.
102
+ if (
103
+ type !== null &&
104
+ type !== undefined &&
105
+ typeof type.render === 'function'
106
+ ) {
107
+ // ForwardRef is special because its resolved .type is an object,
108
+ // but it's possible that we only have its inner render function in the map.
109
+ // If that inner render function is different, we'll build a new forwardRef type.
110
+ const currentRender = resolveFunctionForHotReloading(type.render);
111
+ if (type.render !== currentRender) {
112
+ const syntheticType = {
113
+ $$typeof: REACT_FORWARD_REF_TYPE,
114
+ render: currentRender,
115
+ };
116
+ if (type.displayName !== undefined) {
117
+ (syntheticType: any).displayName = type.displayName;
118
+ }
119
+ return syntheticType;
120
+ }
121
+ }
122
+ return type;
123
+ }
124
+ // Use the latest known implementation.
125
+ return family.current;
126
+ } else {
127
+ return type;
128
+ }
129
+}
130
+
131
+export function isCompatibleFamilyForHotReloading(
132
+ fiber: Fiber,
133
+ element: ReactElement,
134
+): boolean {
135
+ if (__DEV__) {
136
+ if (resolveFamily === null) {
137
+ // Hot reloading is disabled.
138
+ return false;
139
+ }
140
+
141
+ const prevType = fiber.elementType;
142
+ const nextType = element.type;
143
+
144
+ // If we got here, we know types aren't === equal.
145
+ let needsCompareFamilies = false;
146
+
147
+ const $$typeofNextType =
148
+ typeof nextType === 'object' && nextType !== null
149
+ ? nextType.$$typeof
150
+ : null;
151
+
152
+ switch (fiber.tag) {
153
+ case ClassComponent: {
154
+ if (typeof nextType === 'function') {
155
+ needsCompareFamilies = true;
156
+ }
157
+ break;
158
+ }
159
+ case FunctionComponent: {
160
+ if (typeof nextType === 'function') {
161
+ needsCompareFamilies = true;
162
+ } else if ($$typeofNextType === REACT_LAZY_TYPE) {
163
+ // We don't know the inner type yet.
164
+ // We're going to assume that the lazy inner type is stable,
165
+ // and so it is sufficient to avoid reconciling it away.
166
+ // We're not going to unwrap or actually use the new lazy type.
167
+ needsCompareFamilies = true;
168
+ }
169
+ break;
170
+ }
171
+ case ForwardRef: {
172
+ if ($$typeofNextType === REACT_FORWARD_REF_TYPE) {
173
+ needsCompareFamilies = true;
174
+ } else if ($$typeofNextType === REACT_LAZY_TYPE) {
175
+ needsCompareFamilies = true;
176
+ }
177
+ break;
178
+ }
179
+ case MemoComponent:
180
+ case SimpleMemoComponent: {
181
+ if ($$typeofNextType === REACT_MEMO_TYPE) {
182
+ // TODO: if it was but can no longer be simple,
183
+ // we shouldn't set this.
184
+ needsCompareFamilies = true;
185
+ } else if ($$typeofNextType === REACT_LAZY_TYPE) {
186
+ needsCompareFamilies = true;
187
+ }
188
+ break;
189
+ }
190
+ default:
191
+ return false;
192
+ }
193
+
194
+ // Check if both types have a family and it's the same one.
195
+ if (needsCompareFamilies) {
196
+ // Note: memo() and forwardRef() we'll compare outer rather than inner type.
197
+ // This means both of them need to be registered to preserve state.
198
+ // If we unwrapped and compared the inner types for wrappers instead,
199
+ // then we would risk falsely saying two separate memo(Foo)
200
+ // calls are equivalent because they wrap the same Foo function.
201
+ const prevFamily = resolveFamily(prevType);
202
+ if (prevFamily !== undefined && prevFamily === resolveFamily(nextType)) {
203
+ return true;
204
+ }
205
+ }
206
+ return false;
207
+ } else {
208
+ return false;
209
+ }
210
+}
211
+
212
+export function markFailedErrorBoundaryForHotReloading(fiber: Fiber) {
213
+ if (__DEV__) {
214
+ if (resolveFamily === null) {
215
+ // Hot reloading is disabled.
216
+ return;
217
+ }
218
+ if (typeof WeakSet !== 'function') {
219
+ return;
220
+ }
221
+ if (failedBoundaries === null) {
222
+ failedBoundaries = new WeakSet();
223
+ }
224
+ failedBoundaries.add(fiber);
225
+ }
226
+}
227
+
228
+export const scheduleRefresh: ScheduleRefresh = (
229
+ root: FiberRoot,
230
+ update: RefreshUpdate,
231
+): void => {
232
+ if (__DEV__) {
233
+ if (resolveFamily === null) {
234
+ // Hot reloading is disabled.
235
+ return;
236
+ }
237
+ const {staleFamilies, updatedFamilies} = update;
238
+ flushPassiveEffects();
239
+ flushSync(() => {
240
+ scheduleFibersWithFamiliesRecursively(
241
+ root.current,
242
+ updatedFamilies,
243
+ staleFamilies,
244
+ );
245
+ });
246
+ }
247
+};
248
+
249
+export const scheduleRoot: ScheduleRoot = (
250
+ root: FiberRoot,
251
+ element: ReactNodeList,
252
+): void => {
253
+ if (__DEV__) {
254
+ if (root.context !== emptyContextObject) {
255
+ // Super edge case: root has a legacy _renderSubtree context
256
+ // but we don't know the parentComponent so we can't pass it.
257
+ // Just ignore. We'll delete this with _renderSubtree code path later.
258
+ return;
259
+ }
260
+ flushPassiveEffects();
261
+ syncUpdates(() => {
262
+ updateContainer(element, root, null, null);
263
+ });
264
+ }
265
+};
266
+
267
+function scheduleFibersWithFamiliesRecursively(
268
+ fiber: Fiber,
269
+ updatedFamilies: Set<Family>,
270
+ staleFamilies: Set<Family>,
271
+) {
272
+ if (__DEV__) {
273
+ const {alternate, child, sibling, tag, type} = fiber;
274
+
275
+ let candidateType = null;
276
+ switch (tag) {
277
+ case FunctionComponent:
278
+ case SimpleMemoComponent:
279
+ case ClassComponent:
280
+ candidateType = type;
281
+ break;
282
+ case ForwardRef:
283
+ candidateType = type.render;
284
+ break;
285
+ default:
286
+ break;
287
+ }
288
+
289
+ if (resolveFamily === null) {
290
+ throw new Error('Expected resolveFamily to be set during hot reload.');
291
+ }
292
+
293
+ let needsRender = false;
294
+ let needsRemount = false;
295
+ if (candidateType !== null) {
296
+ const family = resolveFamily(candidateType);
297
+ if (family !== undefined) {
298
+ if (staleFamilies.has(family)) {
299
+ needsRemount = true;
300
+ } else if (updatedFamilies.has(family)) {
301
+ if (tag === ClassComponent) {
302
+ needsRemount = true;
303
+ } else {
304
+ needsRender = true;
305
+ }
306
+ }
307
+ }
308
+ }
309
+ if (failedBoundaries !== null) {
310
+ if (
311
+ failedBoundaries.has(fiber) ||
312
+ (alternate !== null && failedBoundaries.has(alternate))
313
+ ) {
314
+ needsRemount = true;
315
+ }
316
+ }
317
+
318
+ if (needsRemount) {
319
+ fiber._debugNeedsRemount = true;
320
+ }
321
+ if (needsRemount || needsRender) {
322
+ scheduleUpdateOnFiber(fiber, Sync);
323
+ }
324
+ if (child !== null && !needsRemount) {
325
+ scheduleFibersWithFamiliesRecursively(
326
+ child,
327
+ updatedFamilies,
328
+ staleFamilies,
329
+ );
330
+ }
331
+ if (sibling !== null) {
332
+ scheduleFibersWithFamiliesRecursively(
333
+ sibling,
334
+ updatedFamilies,
335
+ staleFamilies,
336
+ );
337
+ }
338
+ }
339
+}
340
+
341
+export const findHostInstancesForRefresh: FindHostInstancesForRefresh = (
342
+ root: FiberRoot,
343
+ families: Array<Family>,
344
+): Set<Instance> => {
345
+ if (__DEV__) {
346
+ const hostInstances = new Set();
347
+ const types = new Set(families.map(family => family.current));
348
+ findHostInstancesForMatchingFibersRecursively(
349
+ root.current,
350
+ types,
351
+ hostInstances,
352
+ );
353
+ return hostInstances;
354
+ } else {
355
+ throw new Error(
356
+ 'Did not expect findHostInstancesForRefresh to be called in production.',
357
+ );
358
+ }
359
+};
360
+
361
+function findHostInstancesForMatchingFibersRecursively(
362
+ fiber: Fiber,
363
+ types: Set<any>,
364
+ hostInstances: Set<Instance>,
365
+) {
366
+ if (__DEV__) {
367
+ const {child, sibling, tag, type} = fiber;
368
+
369
+ let candidateType = null;
370
+ switch (tag) {
371
+ case FunctionComponent:
372
+ case SimpleMemoComponent:
373
+ case ClassComponent:
374
+ candidateType = type;
375
+ break;
376
+ case ForwardRef:
377
+ candidateType = type.render;
378
+ break;
379
+ default:
380
+ break;
381
+ }
382
+
383
+ let didMatch = false;
384
+ if (candidateType !== null) {
385
+ if (types.has(candidateType)) {
386
+ didMatch = true;
387
+ }
388
+ }
389
+
390
+ if (didMatch) {
391
+ // We have a match. This only drills down to the closest host components.
392
+ // There's no need to search deeper because for the purpose of giving
393
+ // visual feedback, "flashing" outermost parent rectangles is sufficient.
394
+ findHostInstancesForFiberShallowly(fiber, hostInstances);
395
+ } else {
396
+ // If there's no match, maybe there will be one further down in the child tree.
397
+ if (child !== null) {
398
+ findHostInstancesForMatchingFibersRecursively(
399
+ child,
400
+ types,
401
+ hostInstances,
402
+ );
403
+ }
404
+ }
405
+
406
+ if (sibling !== null) {
407
+ findHostInstancesForMatchingFibersRecursively(
408
+ sibling,
409
+ types,
410
+ hostInstances,
411
+ );
412
+ }
413
+ }
414
+}
415
+
416
+function findHostInstancesForFiberShallowly(
417
+ fiber: Fiber,
418
+ hostInstances: Set<Instance>,
419
+): void {
420
+ if (__DEV__) {
421
+ const foundHostInstances = findChildHostInstancesForFiberShallowly(
422
+ fiber,
423
+ hostInstances,
424
+ );
425
+ if (foundHostInstances) {
426
+ return;
427
+ }
428
+ // If we didn't find any host children, fallback to closest host parent.
429
+ let node = fiber;
430
+ while (true) {
431
+ switch (node.tag) {
432
+ case HostComponent:
433
+ hostInstances.add(node.stateNode);
434
+ return;
435
+ case HostPortal:
436
+ hostInstances.add(node.stateNode.containerInfo);
437
+ return;
438
+ case HostRoot:
439
+ hostInstances.add(node.stateNode.containerInfo);
440
+ return;
441
+ }
442
+ if (node.return === null) {
443
+ throw new Error('Expected to reach root first.');
444
+ }
445
+ node = node.return;
446
+ }
447
+ }
448
+}
449
+
450
+function findChildHostInstancesForFiberShallowly(
451
+ fiber: Fiber,
452
+ hostInstances: Set<Instance>,
453
+): boolean {
454
+ if (__DEV__) {
455
+ let node: Fiber = fiber;
456
+ let foundHostInstances = false;
457
+ while (true) {
458
+ if (node.tag === HostComponent) {
459
+ // We got a match.
460
+ foundHostInstances = true;
461
+ hostInstances.add(node.stateNode);
462
+ // There may still be more, so keep searching.
463
+ } else if (node.child !== null) {
464
+ node.child.return = node;
465
+ node = node.child;
466
+ continue;
467
+ }
468
+ if (node === fiber) {
469
+ return foundHostInstances;
470
+ }
471
+ while (node.sibling === null) {
472
+ if (node.return === null || node.return === fiber) {
473
+ return foundHostInstances;
474
+ }
475
+ node = node.return;
476
+ }
477
+ node.sibling.return = node.return;
478
+ node = node.sibling;
479
+ }
480
+ }
481
+ return false;
482
+}
packages/react-reconciler/src/ReactFiberHydrationContext.new.js
new
+503
@@ -0,0 +1,503 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactInternalTypes';
11
+import type {
12
+ Instance,
13
+ TextInstance,
14
+ HydratableInstance,
15
+ SuspenseInstance,
16
+ Container,
17
+ HostContext,
18
+} from './ReactFiberHostConfig';
19
+import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
20
+
21
+import {
22
+ HostComponent,
23
+ HostText,
24
+ HostRoot,
25
+ SuspenseComponent,
26
+} from './ReactWorkTags';
27
+import {Deletion, Placement, Hydrating} from './ReactSideEffectTags';
28
+import invariant from 'shared/invariant';
29
+
30
+import {
31
+ createFiberFromHostInstanceForDeletion,
32
+ createFiberFromDehydratedFragment,
33
+} from './ReactFiber.new';
34
+import {
35
+ shouldSetTextContent,
36
+ supportsHydration,
37
+ canHydrateInstance,
38
+ canHydrateTextInstance,
39
+ canHydrateSuspenseInstance,
40
+ getNextHydratableSibling,
41
+ getFirstHydratableChild,
42
+ hydrateInstance,
43
+ hydrateTextInstance,
44
+ hydrateSuspenseInstance,
45
+ getNextHydratableInstanceAfterSuspenseInstance,
46
+ didNotMatchHydratedContainerTextInstance,
47
+ didNotMatchHydratedTextInstance,
48
+ didNotHydrateContainerInstance,
49
+ didNotHydrateInstance,
50
+ didNotFindHydratableContainerInstance,
51
+ didNotFindHydratableContainerTextInstance,
52
+ didNotFindHydratableContainerSuspenseInstance,
53
+ didNotFindHydratableInstance,
54
+ didNotFindHydratableTextInstance,
55
+ didNotFindHydratableSuspenseInstance,
56
+} from './ReactFiberHostConfig';
57
+import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';
58
+import {Never, NoWork} from './ReactFiberExpirationTime';
59
+
60
+// The deepest Fiber on the stack involved in a hydration context.
61
+// This may have been an insertion or a hydration.
62
+let hydrationParentFiber: null | Fiber = null;
63
+let nextHydratableInstance: null | HydratableInstance = null;
64
+let isHydrating: boolean = false;
65
+
66
+function warnIfHydrating() {
67
+ if (__DEV__) {
68
+ if (isHydrating) {
69
+ console.error(
70
+ 'We should not be hydrating here. This is a bug in React. Please file a bug.',
71
+ );
72
+ }
73
+ }
74
+}
75
+
76
+function enterHydrationState(fiber: Fiber): boolean {
77
+ if (!supportsHydration) {
78
+ return false;
79
+ }
80
+
81
+ const parentInstance = fiber.stateNode.containerInfo;
82
+ nextHydratableInstance = getFirstHydratableChild(parentInstance);
83
+ hydrationParentFiber = fiber;
84
+ isHydrating = true;
85
+ return true;
86
+}
87
+
88
+function reenterHydrationStateFromDehydratedSuspenseInstance(
89
+ fiber: Fiber,
90
+ suspenseInstance: SuspenseInstance,
91
+): boolean {
92
+ if (!supportsHydration) {
93
+ return false;
94
+ }
95
+ nextHydratableInstance = getNextHydratableSibling(suspenseInstance);
96
+ popToNextHostParent(fiber);
97
+ isHydrating = true;
98
+ return true;
99
+}
100
+
101
+function deleteHydratableInstance(
102
+ returnFiber: Fiber,
103
+ instance: HydratableInstance,
104
+) {
105
+ if (__DEV__) {
106
+ switch (returnFiber.tag) {
107
+ case HostRoot:
108
+ didNotHydrateContainerInstance(
109
+ returnFiber.stateNode.containerInfo,
110
+ instance,
111
+ );
112
+ break;
113
+ case HostComponent:
114
+ didNotHydrateInstance(
115
+ returnFiber.type,
116
+ returnFiber.memoizedProps,
117
+ returnFiber.stateNode,
118
+ instance,
119
+ );
120
+ break;
121
+ }
122
+ }
123
+
124
+ const childToDelete = createFiberFromHostInstanceForDeletion();
125
+ childToDelete.stateNode = instance;
126
+ childToDelete.return = returnFiber;
127
+ childToDelete.effectTag = Deletion;
128
+
129
+ // This might seem like it belongs on progressedFirstDeletion. However,
130
+ // these children are not part of the reconciliation list of children.
131
+ // Even if we abort and rereconcile the children, that will try to hydrate
132
+ // again and the nodes are still in the host tree so these will be
133
+ // recreated.
134
+ if (returnFiber.lastEffect !== null) {
135
+ returnFiber.lastEffect.nextEffect = childToDelete;
136
+ returnFiber.lastEffect = childToDelete;
137
+ } else {
138
+ returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
139
+ }
140
+}
141
+
142
+function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
143
+ fiber.effectTag = (fiber.effectTag & ~Hydrating) | Placement;
144
+ if (__DEV__) {
145
+ switch (returnFiber.tag) {
146
+ case HostRoot: {
147
+ const parentContainer = returnFiber.stateNode.containerInfo;
148
+ switch (fiber.tag) {
149
+ case HostComponent:
150
+ const type = fiber.type;
151
+ const props = fiber.pendingProps;
152
+ didNotFindHydratableContainerInstance(parentContainer, type, props);
153
+ break;
154
+ case HostText:
155
+ const text = fiber.pendingProps;
156
+ didNotFindHydratableContainerTextInstance(parentContainer, text);
157
+ break;
158
+ case SuspenseComponent:
159
+ didNotFindHydratableContainerSuspenseInstance(parentContainer);
160
+ break;
161
+ }
162
+ break;
163
+ }
164
+ case HostComponent: {
165
+ const parentType = returnFiber.type;
166
+ const parentProps = returnFiber.memoizedProps;
167
+ const parentInstance = returnFiber.stateNode;
168
+ switch (fiber.tag) {
169
+ case HostComponent:
170
+ const type = fiber.type;
171
+ const props = fiber.pendingProps;
172
+ didNotFindHydratableInstance(
173
+ parentType,
174
+ parentProps,
175
+ parentInstance,
176
+ type,
177
+ props,
178
+ );
179
+ break;
180
+ case HostText:
181
+ const text = fiber.pendingProps;
182
+ didNotFindHydratableTextInstance(
183
+ parentType,
184
+ parentProps,
185
+ parentInstance,
186
+ text,
187
+ );
188
+ break;
189
+ case SuspenseComponent:
190
+ didNotFindHydratableSuspenseInstance(
191
+ parentType,
192
+ parentProps,
193
+ parentInstance,
194
+ );
195
+ break;
196
+ }
197
+ break;
198
+ }
199
+ default:
200
+ return;
201
+ }
202
+ }
203
+}
204
+
205
+function tryHydrate(fiber, nextInstance) {
206
+ switch (fiber.tag) {
207
+ case HostComponent: {
208
+ const type = fiber.type;
209
+ const props = fiber.pendingProps;
210
+ const instance = canHydrateInstance(nextInstance, type, props);
211
+ if (instance !== null) {
212
+ fiber.stateNode = (instance: Instance);
213
+ return true;
214
+ }
215
+ return false;
216
+ }
217
+ case HostText: {
218
+ const text = fiber.pendingProps;
219
+ const textInstance = canHydrateTextInstance(nextInstance, text);
220
+ if (textInstance !== null) {
221
+ fiber.stateNode = (textInstance: TextInstance);
222
+ return true;
223
+ }
224
+ return false;
225
+ }
226
+ case SuspenseComponent: {
227
+ if (enableSuspenseServerRenderer) {
228
+ const suspenseInstance: null | SuspenseInstance = canHydrateSuspenseInstance(
229
+ nextInstance,
230
+ );
231
+ if (suspenseInstance !== null) {
232
+ const suspenseState: SuspenseState = {
233
+ dehydrated: suspenseInstance,
234
+ baseTime: NoWork,
235
+ retryTime: Never,
236
+ };
237
+ fiber.memoizedState = suspenseState;
238
+ // Store the dehydrated fragment as a child fiber.
239
+ // This simplifies the code for getHostSibling and deleting nodes,
240
+ // since it doesn't have to consider all Suspense boundaries and
241
+ // check if they're dehydrated ones or not.
242
+ const dehydratedFragment = createFiberFromDehydratedFragment(
243
+ suspenseInstance,
244
+ );
245
+ dehydratedFragment.return = fiber;
246
+ fiber.child = dehydratedFragment;
247
+ return true;
248
+ }
249
+ }
250
+ return false;
251
+ }
252
+ default:
253
+ return false;
254
+ }
255
+}
256
+
257
+function tryToClaimNextHydratableInstance(fiber: Fiber): void {
258
+ if (!isHydrating) {
259
+ return;
260
+ }
261
+ let nextInstance = nextHydratableInstance;
262
+ if (!nextInstance) {
263
+ // Nothing to hydrate. Make it an insertion.
264
+ insertNonHydratedInstance((hydrationParentFiber: any), fiber);
265
+ isHydrating = false;
266
+ hydrationParentFiber = fiber;
267
+ return;
268
+ }
269
+ const firstAttemptedInstance = nextInstance;
270
+ if (!tryHydrate(fiber, nextInstance)) {
271
+ // If we can't hydrate this instance let's try the next one.
272
+ // We use this as a heuristic. It's based on intuition and not data so it
273
+ // might be flawed or unnecessary.
274
+ nextInstance = getNextHydratableSibling(firstAttemptedInstance);
275
+ if (!nextInstance || !tryHydrate(fiber, nextInstance)) {
276
+ // Nothing to hydrate. Make it an insertion.
277
+ insertNonHydratedInstance((hydrationParentFiber: any), fiber);
278
+ isHydrating = false;
279
+ hydrationParentFiber = fiber;
280
+ return;
281
+ }
282
+ // We matched the next one, we'll now assume that the first one was
283
+ // superfluous and we'll delete it. Since we can't eagerly delete it
284
+ // we'll have to schedule a deletion. To do that, this node needs a dummy
285
+ // fiber associated with it.
286
+ deleteHydratableInstance(
287
+ (hydrationParentFiber: any),
288
+ firstAttemptedInstance,
289
+ );
290
+ }
291
+ hydrationParentFiber = fiber;
292
+ nextHydratableInstance = getFirstHydratableChild((nextInstance: any));
293
+}
294
+
295
+function prepareToHydrateHostInstance(
296
+ fiber: Fiber,
297
+ rootContainerInstance: Container,
298
+ hostContext: HostContext,
299
+): boolean {
300
+ if (!supportsHydration) {
301
+ invariant(
302
+ false,
303
+ 'Expected prepareToHydrateHostInstance() to never be called. ' +
304
+ 'This error is likely caused by a bug in React. Please file an issue.',
305
+ );
306
+ }
307
+
308
+ const instance: Instance = fiber.stateNode;
309
+ const updatePayload = hydrateInstance(
310
+ instance,
311
+ fiber.type,
312
+ fiber.memoizedProps,
313
+ rootContainerInstance,
314
+ hostContext,
315
+ fiber,
316
+ );
317
+ // TODO: Type this specific to this type of component.
318
+ fiber.updateQueue = (updatePayload: any);
319
+ // If the update payload indicates that there is a change or if there
320
+ // is a new ref we mark this as an update.
321
+ if (updatePayload !== null) {
322
+ return true;
323
+ }
324
+ return false;
325
+}
326
+
327
+function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
328
+ if (!supportsHydration) {
329
+ invariant(
330
+ false,
331
+ 'Expected prepareToHydrateHostTextInstance() to never be called. ' +
332
+ 'This error is likely caused by a bug in React. Please file an issue.',
333
+ );
334
+ }
335
+
336
+ const textInstance: TextInstance = fiber.stateNode;
337
+ const textContent: string = fiber.memoizedProps;
338
+ const shouldUpdate = hydrateTextInstance(textInstance, textContent, fiber);
339
+ if (__DEV__) {
340
+ if (shouldUpdate) {
341
+ // We assume that prepareToHydrateHostTextInstance is called in a context where the
342
+ // hydration parent is the parent host component of this host text.
343
+ const returnFiber = hydrationParentFiber;
344
+ if (returnFiber !== null) {
345
+ switch (returnFiber.tag) {
346
+ case HostRoot: {
347
+ const parentContainer = returnFiber.stateNode.containerInfo;
348
+ didNotMatchHydratedContainerTextInstance(
349
+ parentContainer,
350
+ textInstance,
351
+ textContent,
352
+ );
353
+ break;
354
+ }
355
+ case HostComponent: {
356
+ const parentType = returnFiber.type;
357
+ const parentProps = returnFiber.memoizedProps;
358
+ const parentInstance = returnFiber.stateNode;
359
+ didNotMatchHydratedTextInstance(
360
+ parentType,
361
+ parentProps,
362
+ parentInstance,
363
+ textInstance,
364
+ textContent,
365
+ );
366
+ break;
367
+ }
368
+ }
369
+ }
370
+ }
371
+ }
372
+ return shouldUpdate;
373
+}
374
+
375
+function prepareToHydrateHostSuspenseInstance(fiber: Fiber): void {
376
+ if (!supportsHydration) {
377
+ invariant(
378
+ false,
379
+ 'Expected prepareToHydrateHostSuspenseInstance() to never be called. ' +
380
+ 'This error is likely caused by a bug in React. Please file an issue.',
381
+ );
382
+ }
383
+
384
+ const suspenseState: null | SuspenseState = fiber.memoizedState;
385
+ const suspenseInstance: null | SuspenseInstance =
386
+ suspenseState !== null ? suspenseState.dehydrated : null;
387
+ invariant(
388
+ suspenseInstance,
389
+ 'Expected to have a hydrated suspense instance. ' +
390
+ 'This error is likely caused by a bug in React. Please file an issue.',
391
+ );
392
+ hydrateSuspenseInstance(suspenseInstance, fiber);
393
+}
394
+
395
+function skipPastDehydratedSuspenseInstance(
396
+ fiber: Fiber,
397
+): null | HydratableInstance {
398
+ if (!supportsHydration) {
399
+ invariant(
400
+ false,
401
+ 'Expected skipPastDehydratedSuspenseInstance() to never be called. ' +
402
+ 'This error is likely caused by a bug in React. Please file an issue.',
403
+ );
404
+ }
405
+ const suspenseState: null | SuspenseState = fiber.memoizedState;
406
+ const suspenseInstance: null | SuspenseInstance =
407
+ suspenseState !== null ? suspenseState.dehydrated : null;
408
+ invariant(
409
+ suspenseInstance,
410
+ 'Expected to have a hydrated suspense instance. ' +
411
+ 'This error is likely caused by a bug in React. Please file an issue.',
412
+ );
413
+ return getNextHydratableInstanceAfterSuspenseInstance(suspenseInstance);
414
+}
415
+
416
+function popToNextHostParent(fiber: Fiber): void {
417
+ let parent = fiber.return;
418
+ while (
419
+ parent !== null &&
420
+ parent.tag !== HostComponent &&
421
+ parent.tag !== HostRoot &&
422
+ parent.tag !== SuspenseComponent
423
+ ) {
424
+ parent = parent.return;
425
+ }
426
+ hydrationParentFiber = parent;
427
+}
428
+
429
+function popHydrationState(fiber: Fiber): boolean {
430
+ if (!supportsHydration) {
431
+ return false;
432
+ }
433
+ if (fiber !== hydrationParentFiber) {
434
+ // We're deeper than the current hydration context, inside an inserted
435
+ // tree.
436
+ return false;
437
+ }
438
+ if (!isHydrating) {
439
+ // If we're not currently hydrating but we're in a hydration context, then
440
+ // we were an insertion and now need to pop up reenter hydration of our
441
+ // siblings.
442
+ popToNextHostParent(fiber);
443
+ isHydrating = true;
444
+ return false;
445
+ }
446
+
447
+ const type = fiber.type;
448
+
449
+ // If we have any remaining hydratable nodes, we need to delete them now.
450
+ // We only do this deeper than head and body since they tend to have random
451
+ // other nodes in them. We also ignore components with pure text content in
452
+ // side of them.
453
+ // TODO: Better heuristic.
454
+ if (
455
+ fiber.tag !== HostComponent ||
456
+ (type !== 'head' &&
457
+ type !== 'body' &&
458
+ !shouldSetTextContent(type, fiber.memoizedProps))
459
+ ) {
460
+ let nextInstance = nextHydratableInstance;
461
+ while (nextInstance) {
462
+ deleteHydratableInstance(fiber, nextInstance);
463
+ nextInstance = getNextHydratableSibling(nextInstance);
464
+ }
465
+ }
466
+
467
+ popToNextHostParent(fiber);
468
+ if (fiber.tag === SuspenseComponent) {
469
+ nextHydratableInstance = skipPastDehydratedSuspenseInstance(fiber);
470
+ } else {
471
+ nextHydratableInstance = hydrationParentFiber
472
+ ? getNextHydratableSibling(fiber.stateNode)
473
+ : null;
474
+ }
475
+ return true;
476
+}
477
+
478
+function resetHydrationState(): void {
479
+ if (!supportsHydration) {
480
+ return;
481
+ }
482
+
483
+ hydrationParentFiber = null;
484
+ nextHydratableInstance = null;
485
+ isHydrating = false;
486
+}
487
+
488
+function getIsHydrating(): boolean {
489
+ return isHydrating;
490
+}
491
+
492
+export {
493
+ warnIfHydrating,
494
+ enterHydrationState,
495
+ getIsHydrating,
496
+ reenterHydrationStateFromDehydratedSuspenseInstance,
497
+ resetHydrationState,
498
+ tryToClaimNextHydratableInstance,
499
+ prepareToHydrateHostInstance,
500
+ prepareToHydrateHostTextInstance,
501
+ prepareToHydrateHostSuspenseInstance,
502
+ popHydrationState,
503
+};
packages/react-reconciler/src/ReactFiberLazyComponent.new.js
new
+23
@@ -0,0 +1,23 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+export function resolveDefaultProps(Component: any, baseProps: Object): Object {
11
+ if (Component && Component.defaultProps) {
12
+ // Resolve default props. Taken from ReactElement
13
+ const props = Object.assign({}, baseProps);
14
+ const defaultProps = Component.defaultProps;
15
+ for (const propName in defaultProps) {
16
+ if (props[propName] === undefined) {
17
+ props[propName] = defaultProps[propName];
18
+ }
19
+ }
20
+ return props;
21
+ }
22
+ return baseProps;
23
+}
packages/react-reconciler/src/ReactFiberNewContext.new.js
new
+389
@@ -0,0 +1,389 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {ReactContext} from 'shared/ReactTypes';
11
+import type {Fiber, ContextDependency} from './ReactInternalTypes';
12
+import type {StackCursor} from './ReactFiberStack.new';
13
+import type {ExpirationTime} from './ReactFiberExpirationTime';
14
+
15
+import {isPrimaryRenderer} from './ReactFiberHostConfig';
16
+import {createCursor, push, pop} from './ReactFiberStack.new';
17
+import {MAX_SIGNED_31_BIT_INT} from './MaxInts';
18
+import {
19
+ ContextProvider,
20
+ ClassComponent,
21
+ DehydratedFragment,
22
+} from './ReactWorkTags';
23
+
24
+import invariant from 'shared/invariant';
25
+import is from 'shared/objectIs';
26
+import {createUpdate, enqueueUpdate, ForceUpdate} from './ReactUpdateQueue.new';
27
+import {NoWork} from './ReactFiberExpirationTime';
28
+import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.new';
29
+import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';
30
+
31
+const valueCursor: StackCursor<mixed> = createCursor(null);
32
+
33
+let rendererSigil;
34
+if (__DEV__) {
35
+ // Use this to detect multiple renderers using the same context
36
+ rendererSigil = {};
37
+}
38
+
39
+let currentlyRenderingFiber: Fiber | null = null;
40
+let lastContextDependency: ContextDependency<mixed> | null = null;
41
+let lastContextWithAllBitsObserved: ReactContext<any> | null = null;
42
+
43
+let isDisallowedContextReadInDEV: boolean = false;
44
+
45
+export function resetContextDependencies(): void {
46
+ // This is called right before React yields execution, to ensure `readContext`
47
+ // cannot be called outside the render phase.
48
+ currentlyRenderingFiber = null;
49
+ lastContextDependency = null;
50
+ lastContextWithAllBitsObserved = null;
51
+ if (__DEV__) {
52
+ isDisallowedContextReadInDEV = false;
53
+ }
54
+}
55
+
56
+export function enterDisallowedContextReadInDEV(): void {
57
+ if (__DEV__) {
58
+ isDisallowedContextReadInDEV = true;
59
+ }
60
+}
61
+
62
+export function exitDisallowedContextReadInDEV(): void {
63
+ if (__DEV__) {
64
+ isDisallowedContextReadInDEV = false;
65
+ }
66
+}
67
+
68
+export function pushProvider<T>(providerFiber: Fiber, nextValue: T): void {
69
+ const context: ReactContext<T> = providerFiber.type._context;
70
+
71
+ if (isPrimaryRenderer) {
72
+ push(valueCursor, context._currentValue, providerFiber);
73
+
74
+ context._currentValue = nextValue;
75
+ if (__DEV__) {
76
+ if (
77
+ context._currentRenderer !== undefined &&
78
+ context._currentRenderer !== null &&
79
+ context._currentRenderer !== rendererSigil
80
+ ) {
81
+ console.error(
82
+ 'Detected multiple renderers concurrently rendering the ' +
83
+ 'same context provider. This is currently unsupported.',
84
+ );
85
+ }
86
+ context._currentRenderer = rendererSigil;
87
+ }
88
+ } else {
89
+ push(valueCursor, context._currentValue2, providerFiber);
90
+
91
+ context._currentValue2 = nextValue;
92
+ if (__DEV__) {
93
+ if (
94
+ context._currentRenderer2 !== undefined &&
95
+ context._currentRenderer2 !== null &&
96
+ context._currentRenderer2 !== rendererSigil
97
+ ) {
98
+ console.error(
99
+ 'Detected multiple renderers concurrently rendering the ' +
100
+ 'same context provider. This is currently unsupported.',
101
+ );
102
+ }
103
+ context._currentRenderer2 = rendererSigil;
104
+ }
105
+ }
106
+}
107
+
108
+export function popProvider(providerFiber: Fiber): void {
109
+ const currentValue = valueCursor.current;
110
+
111
+ pop(valueCursor, providerFiber);
112
+
113
+ const context: ReactContext<any> = providerFiber.type._context;
114
+ if (isPrimaryRenderer) {
115
+ context._currentValue = currentValue;
116
+ } else {
117
+ context._currentValue2 = currentValue;
118
+ }
119
+}
120
+
121
+export function calculateChangedBits<T>(
122
+ context: ReactContext<T>,
123
+ newValue: T,
124
+ oldValue: T,
125
+) {
126
+ if (is(oldValue, newValue)) {
127
+ // No change
128
+ return 0;
129
+ } else {
130
+ const changedBits =
131
+ typeof context._calculateChangedBits === 'function'
132
+ ? context._calculateChangedBits(oldValue, newValue)
133
+ : MAX_SIGNED_31_BIT_INT;
134
+
135
+ if (__DEV__) {
136
+ if ((changedBits & MAX_SIGNED_31_BIT_INT) !== changedBits) {
137
+ console.error(
138
+ 'calculateChangedBits: Expected the return value to be a ' +
139
+ '31-bit integer. Instead received: %s',
140
+ changedBits,
141
+ );
142
+ }
143
+ }
144
+ return changedBits | 0;
145
+ }
146
+}
147
+
148
+export function scheduleWorkOnParentPath(
149
+ parent: Fiber | null,
150
+ renderExpirationTime: ExpirationTime,
151
+) {
152
+ // Update the child expiration time of all the ancestors, including
153
+ // the alternates.
154
+ let node = parent;
155
+ while (node !== null) {
156
+ const alternate = node.alternate;
157
+ if (node.childExpirationTime < renderExpirationTime) {
158
+ node.childExpirationTime = renderExpirationTime;
159
+ if (
160
+ alternate !== null &&
161
+ alternate.childExpirationTime < renderExpirationTime
162
+ ) {
163
+ alternate.childExpirationTime = renderExpirationTime;
164
+ }
165
+ } else if (
166
+ alternate !== null &&
167
+ alternate.childExpirationTime < renderExpirationTime
168
+ ) {
169
+ alternate.childExpirationTime = renderExpirationTime;
170
+ } else {
171
+ // Neither alternate was updated, which means the rest of the
172
+ // ancestor path already has sufficient priority.
173
+ break;
174
+ }
175
+ node = node.return;
176
+ }
177
+}
178
+
179
+export function propagateContextChange(
180
+ workInProgress: Fiber,
181
+ context: ReactContext<mixed>,
182
+ changedBits: number,
183
+ renderExpirationTime: ExpirationTime,
184
+): void {
185
+ let fiber = workInProgress.child;
186
+ if (fiber !== null) {
187
+ // Set the return pointer of the child to the work-in-progress fiber.
188
+ fiber.return = workInProgress;
189
+ }
190
+ while (fiber !== null) {
191
+ let nextFiber;
192
+
193
+ // Visit this fiber.
194
+ const list = fiber.dependencies;
195
+ if (list !== null) {
196
+ nextFiber = fiber.child;
197
+
198
+ let dependency = list.firstContext;
199
+ while (dependency !== null) {
200
+ // Check if the context matches.
201
+ if (
202
+ dependency.context === context &&
203
+ (dependency.observedBits & changedBits) !== 0
204
+ ) {
205
+ // Match! Schedule an update on this fiber.
206
+
207
+ if (fiber.tag === ClassComponent) {
208
+ // Schedule a force update on the work-in-progress.
209
+ const update = createUpdate(renderExpirationTime, null);
210
+ update.tag = ForceUpdate;
211
+ // TODO: Because we don't have a work-in-progress, this will add the
212
+ // update to the current fiber, too, which means it will persist even if
213
+ // this render is thrown away. Since it's a race condition, not sure it's
214
+ // worth fixing.
215
+ enqueueUpdate(fiber, update);
216
+ }
217
+
218
+ if (fiber.expirationTime < renderExpirationTime) {
219
+ fiber.expirationTime = renderExpirationTime;
220
+ }
221
+ const alternate = fiber.alternate;
222
+ if (
223
+ alternate !== null &&
224
+ alternate.expirationTime < renderExpirationTime
225
+ ) {
226
+ alternate.expirationTime = renderExpirationTime;
227
+ }
228
+
229
+ scheduleWorkOnParentPath(fiber.return, renderExpirationTime);
230
+
231
+ // Mark the expiration time on the list, too.
232
+ if (list.expirationTime < renderExpirationTime) {
233
+ list.expirationTime = renderExpirationTime;
234
+ }
235
+
236
+ // Since we already found a match, we can stop traversing the
237
+ // dependency list.
238
+ break;
239
+ }
240
+ dependency = dependency.next;
241
+ }
242
+ } else if (fiber.tag === ContextProvider) {
243
+ // Don't scan deeper if this is a matching provider
244
+ nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
245
+ } else if (
246
+ enableSuspenseServerRenderer &&
247
+ fiber.tag === DehydratedFragment
248
+ ) {
249
+ // If a dehydrated suspense bounudary is in this subtree, we don't know
250
+ // if it will have any context consumers in it. The best we can do is
251
+ // mark it as having updates.
252
+ const parentSuspense = fiber.return;
253
+ invariant(
254
+ parentSuspense !== null,
255
+ 'We just came from a parent so we must have had a parent. This is a bug in React.',
256
+ );
257
+ if (parentSuspense.expirationTime < renderExpirationTime) {
258
+ parentSuspense.expirationTime = renderExpirationTime;
259
+ }
260
+ const alternate = parentSuspense.alternate;
261
+ if (
262
+ alternate !== null &&
263
+ alternate.expirationTime < renderExpirationTime
264
+ ) {
265
+ alternate.expirationTime = renderExpirationTime;
266
+ }
267
+ // This is intentionally passing this fiber as the parent
268
+ // because we want to schedule this fiber as having work
269
+ // on its children. We'll use the childExpirationTime on
270
+ // this fiber to indicate that a context has changed.
271
+ scheduleWorkOnParentPath(parentSuspense, renderExpirationTime);
272
+ nextFiber = fiber.sibling;
273
+ } else {
274
+ // Traverse down.
275
+ nextFiber = fiber.child;
276
+ }
277
+
278
+ if (nextFiber !== null) {
279
+ // Set the return pointer of the child to the work-in-progress fiber.
280
+ nextFiber.return = fiber;
281
+ } else {
282
+ // No child. Traverse to next sibling.
283
+ nextFiber = fiber;
284
+ while (nextFiber !== null) {
285
+ if (nextFiber === workInProgress) {
286
+ // We're back to the root of this subtree. Exit.
287
+ nextFiber = null;
288
+ break;
289
+ }
290
+ const sibling = nextFiber.sibling;
291
+ if (sibling !== null) {
292
+ // Set the return pointer of the sibling to the work-in-progress fiber.
293
+ sibling.return = nextFiber.return;
294
+ nextFiber = sibling;
295
+ break;
296
+ }
297
+ // No more siblings. Traverse up.
298
+ nextFiber = nextFiber.return;
299
+ }
300
+ }
301
+ fiber = nextFiber;
302
+ }
303
+}
304
+
305
+export function prepareToReadContext(
306
+ workInProgress: Fiber,
307
+ renderExpirationTime: ExpirationTime,
308
+): void {
309
+ currentlyRenderingFiber = workInProgress;
310
+ lastContextDependency = null;
311
+ lastContextWithAllBitsObserved = null;
312
+
313
+ const dependencies = workInProgress.dependencies;
314
+ if (dependencies !== null) {
315
+ const firstContext = dependencies.firstContext;
316
+ if (firstContext !== null) {
317
+ if (dependencies.expirationTime >= renderExpirationTime) {
318
+ // Context list has a pending update. Mark that this fiber performed work.
319
+ markWorkInProgressReceivedUpdate();
320
+ }
321
+ // Reset the work-in-progress list
322
+ dependencies.firstContext = null;
323
+ }
324
+ }
325
+}
326
+
327
+export function readContext<T>(
328
+ context: ReactContext<T>,
329
+ observedBits: void | number | boolean,
330
+): T {
331
+ if (__DEV__) {
332
+ // This warning would fire if you read context inside a Hook like useMemo.
333
+ // Unlike the class check below, it's not enforced in production for perf.
334
+ if (isDisallowedContextReadInDEV) {
335
+ console.error(
336
+ 'Context can only be read while React is rendering. ' +
337
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
338
+ 'In function components, you can read it directly in the function body, but not ' +
339
+ 'inside Hooks like useReducer() or useMemo().',
340
+ );
341
+ }
342
+ }
343
+
344
+ if (lastContextWithAllBitsObserved === context) {
345
+ // Nothing to do. We already observe everything in this context.
346
+ } else if (observedBits === false || observedBits === 0) {
347
+ // Do not observe any updates.
348
+ } else {
349
+ let resolvedObservedBits; // Avoid deopting on observable arguments or heterogeneous types.
350
+ if (
351
+ typeof observedBits !== 'number' ||
352
+ observedBits === MAX_SIGNED_31_BIT_INT
353
+ ) {
354
+ // Observe all updates.
355
+ lastContextWithAllBitsObserved = ((context: any): ReactContext<mixed>);
356
+ resolvedObservedBits = MAX_SIGNED_31_BIT_INT;
357
+ } else {
358
+ resolvedObservedBits = observedBits;
359
+ }
360
+
361
+ const contextItem = {
362
+ context: ((context: any): ReactContext<mixed>),
363
+ observedBits: resolvedObservedBits,
364
+ next: null,
365
+ };
366
+
367
+ if (lastContextDependency === null) {
368
+ invariant(
369
+ currentlyRenderingFiber !== null,
370
+ 'Context can only be read while React is rendering. ' +
371
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
372
+ 'In function components, you can read it directly in the function body, but not ' +
373
+ 'inside Hooks like useReducer() or useMemo().',
374
+ );
375
+
376
+ // This is the first dependency for this component. Create a new list.
377
+ lastContextDependency = contextItem;
378
+ currentlyRenderingFiber.dependencies = {
379
+ expirationTime: NoWork,
380
+ firstContext: contextItem,
381
+ responders: null,
382
+ };
383
+ } else {
384
+ // Append a new context item.
385
+ lastContextDependency = lastContextDependency.next = contextItem;
386
+ }
387
+ }
388
+ return isPrimaryRenderer ? context._currentValue : context._currentValue2;
389
+}
packages/react-reconciler/src/ReactFiberReconciler.new.js
new
+729
@@ -0,0 +1,729 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber, SuspenseHydrationCallbacks} from './ReactInternalTypes';
11
+import type {FiberRoot} from './ReactInternalTypes';
12
+import type {RootTag} from './ReactRootTags';
13
+import type {
14
+ Instance,
15
+ TextInstance,
16
+ Container,
17
+ PublicInstance,
18
+} from './ReactFiberHostConfig';
19
+import type {RendererInspectionConfig} from './ReactFiberHostConfig';
20
+import {FundamentalComponent} from './ReactWorkTags';
21
+import type {ReactNodeList, Thenable} from 'shared/ReactTypes';
22
+import type {ExpirationTime} from './ReactFiberExpirationTime';
23
+import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
24
+
25
+import {
26
+ findCurrentHostFiber,
27
+ findCurrentHostFiberWithNoPortals,
28
+} from './ReactFiberTreeReflection';
29
+import {get as getInstance} from 'shared/ReactInstanceMap';
30
+import {
31
+ HostComponent,
32
+ ClassComponent,
33
+ HostRoot,
34
+ SuspenseComponent,
35
+} from './ReactWorkTags';
36
+import getComponentName from 'shared/getComponentName';
37
+import invariant from 'shared/invariant';
38
+import ReactSharedInternals from 'shared/ReactSharedInternals';
39
+
40
+import {getPublicInstance} from './ReactFiberHostConfig';
41
+import {
42
+ findCurrentUnmaskedContext,
43
+ processChildContext,
44
+ emptyContextObject,
45
+ isContextProvider as isLegacyContextProvider,
46
+} from './ReactFiberContext.new';
47
+import {createFiberRoot} from './ReactFiberRoot.new';
48
+import {injectInternals, onScheduleRoot} from './ReactFiberDevToolsHook.new';
49
+import {
50
+ requestCurrentTimeForUpdate,
51
+ computeExpirationForFiber,
52
+ scheduleUpdateOnFiber,
53
+ flushRoot,
54
+ batchedEventUpdates,
55
+ batchedUpdates,
56
+ unbatchedUpdates,
57
+ flushSync,
58
+ flushControlled,
59
+ deferredUpdates,
60
+ syncUpdates,
61
+ discreteUpdates,
62
+ flushDiscreteUpdates,
63
+ flushPassiveEffects,
64
+ warnIfNotScopedWithMatchingAct,
65
+ warnIfUnmockedScheduler,
66
+ IsThisRendererActing,
67
+} from './ReactFiberWorkLoop.new';
68
+import {createUpdate, enqueueUpdate} from './ReactUpdateQueue.new';
69
+import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
70
+import {
71
+ isRendering as ReactCurrentFiberIsRendering,
72
+ current as ReactCurrentFiberCurrent,
73
+} from './ReactCurrentFiber';
74
+import {StrictMode} from './ReactTypeOfMode';
75
+import {
76
+ Sync,
77
+ ContinuousHydration,
78
+ computeInteractiveExpiration,
79
+} from './ReactFiberExpirationTime';
80
+import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
81
+import {
82
+ scheduleRefresh,
83
+ scheduleRoot,
84
+ setRefreshHandler,
85
+ findHostInstancesForRefresh,
86
+} from './ReactFiberHotReloading.new';
87
+
88
+// used by isTestEnvironment builds
89
+import enqueueTask from 'shared/enqueueTask';
90
+import * as Scheduler from 'scheduler';
91
+// end isTestEnvironment imports
92
+
93
+export {createPortal} from './ReactPortal';
94
+
95
+type OpaqueRoot = FiberRoot;
96
+
97
+// 0 is PROD, 1 is DEV.
98
+// Might add PROFILE later.
99
+type BundleType = 0 | 1;
100
+
101
+type DevToolsConfig = {|
102
+ bundleType: BundleType,
103
+ version: string,
104
+ rendererPackageName: string,
105
+ // Note: this actually *does* depend on Fiber internal fields.
106
+ // Used by "inspect clicked DOM element" in React DevTools.
107
+ findFiberByHostInstance?: (instance: Instance | TextInstance) => Fiber | null,
108
+ rendererConfig?: RendererInspectionConfig,
109
+|};
110
+
111
+let didWarnAboutNestedUpdates;
112
+let didWarnAboutFindNodeInStrictMode;
113
+
114
+if (__DEV__) {
115
+ didWarnAboutNestedUpdates = false;
116
+ didWarnAboutFindNodeInStrictMode = {};
117
+}
118
+
119
+function getContextForSubtree(
120
+ parentComponent: ?React$Component<any, any>,
121
+): Object {
122
+ if (!parentComponent) {
123
+ return emptyContextObject;
124
+ }
125
+
126
+ const fiber = getInstance(parentComponent);
127
+ const parentContext = findCurrentUnmaskedContext(fiber);
128
+
129
+ if (fiber.tag === ClassComponent) {
130
+ const Component = fiber.type;
131
+ if (isLegacyContextProvider(Component)) {
132
+ return processChildContext(fiber, Component, parentContext);
133
+ }
134
+ }
135
+
136
+ return parentContext;
137
+}
138
+
139
+function findHostInstance(component: Object): PublicInstance | null {
140
+ const fiber = getInstance(component);
141
+ if (fiber === undefined) {
142
+ if (typeof component.render === 'function') {
143
+ invariant(false, 'Unable to find node on an unmounted component.');
144
+ } else {
145
+ invariant(
146
+ false,
147
+ 'Argument appears to not be a ReactComponent. Keys: %s',
148
+ Object.keys(component),
149
+ );
150
+ }
151
+ }
152
+ const hostFiber = findCurrentHostFiber(fiber);
153
+ if (hostFiber === null) {
154
+ return null;
155
+ }
156
+ return hostFiber.stateNode;
157
+}
158
+
159
+function findHostInstanceWithWarning(
160
+ component: Object,
161
+ methodName: string,
162
+): PublicInstance | null {
163
+ if (__DEV__) {
164
+ const fiber = getInstance(component);
165
+ if (fiber === undefined) {
166
+ if (typeof component.render === 'function') {
167
+ invariant(false, 'Unable to find node on an unmounted component.');
168
+ } else {
169
+ invariant(
170
+ false,
171
+ 'Argument appears to not be a ReactComponent. Keys: %s',
172
+ Object.keys(component),
173
+ );
174
+ }
175
+ }
176
+ const hostFiber = findCurrentHostFiber(fiber);
177
+ if (hostFiber === null) {
178
+ return null;
179
+ }
180
+ if (hostFiber.mode & StrictMode) {
181
+ const componentName = getComponentName(fiber.type) || 'Component';
182
+ if (!didWarnAboutFindNodeInStrictMode[componentName]) {
183
+ didWarnAboutFindNodeInStrictMode[componentName] = true;
184
+ if (fiber.mode & StrictMode) {
185
+ console.error(
186
+ '%s is deprecated in StrictMode. ' +
187
+ '%s was passed an instance of %s which is inside StrictMode. ' +
188
+ 'Instead, add a ref directly to the element you want to reference. ' +
189
+ 'Learn more about using refs safely here: ' +
190
+ 'https://fb.me/react-strict-mode-find-node%s',
191
+ methodName,
192
+ methodName,
193
+ componentName,
194
+ getStackByFiberInDevAndProd(hostFiber),
195
+ );
196
+ } else {
197
+ console.error(
198
+ '%s is deprecated in StrictMode. ' +
199
+ '%s was passed an instance of %s which renders StrictMode children. ' +
200
+ 'Instead, add a ref directly to the element you want to reference. ' +
201
+ 'Learn more about using refs safely here: ' +
202
+ 'https://fb.me/react-strict-mode-find-node%s',
203
+ methodName,
204
+ methodName,
205
+ componentName,
206
+ getStackByFiberInDevAndProd(hostFiber),
207
+ );
208
+ }
209
+ }
210
+ }
211
+ return hostFiber.stateNode;
212
+ }
213
+ return findHostInstance(component);
214
+}
215
+
216
+export function createContainer(
217
+ containerInfo: Container,
218
+ tag: RootTag,
219
+ hydrate: boolean,
220
+ hydrationCallbacks: null | SuspenseHydrationCallbacks,
221
+): OpaqueRoot {
222
+ return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
223
+}
224
+
225
+export function updateContainer(
226
+ element: ReactNodeList,
227
+ container: OpaqueRoot,
228
+ parentComponent: ?React$Component<any, any>,
229
+ callback: ?Function,
230
+): ExpirationTime {
231
+ if (__DEV__) {
232
+ onScheduleRoot(container, element);
233
+ }
234
+ const current = container.current;
235
+ const currentTime = requestCurrentTimeForUpdate();
236
+ if (__DEV__) {
237
+ // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
238
+ if ('undefined' !== typeof jest) {
239
+ warnIfUnmockedScheduler(current);
240
+ warnIfNotScopedWithMatchingAct(current);
241
+ }
242
+ }
243
+ const suspenseConfig = requestCurrentSuspenseConfig();
244
+ const expirationTime = computeExpirationForFiber(
245
+ currentTime,
246
+ current,
247
+ suspenseConfig,
248
+ );
249
+
250
+ const context = getContextForSubtree(parentComponent);
251
+ if (container.context === null) {
252
+ container.context = context;
253
+ } else {
254
+ container.pendingContext = context;
255
+ }
256
+
257
+ if (__DEV__) {
258
+ if (
259
+ ReactCurrentFiberIsRendering &&
260
+ ReactCurrentFiberCurrent !== null &&
261
+ !didWarnAboutNestedUpdates
262
+ ) {
263
+ didWarnAboutNestedUpdates = true;
264
+ console.error(
265
+ 'Render methods should be a pure function of props and state; ' +
266
+ 'triggering nested component updates from render is not allowed. ' +
267
+ 'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
268
+ 'Check the render method of %s.',
269
+ getComponentName(ReactCurrentFiberCurrent.type) || 'Unknown',
270
+ );
271
+ }
272
+ }
273
+
274
+ const update = createUpdate(expirationTime, suspenseConfig);
275
+ // Caution: React DevTools currently depends on this property
276
+ // being called "element".
277
+ update.payload = {element};
278
+
279
+ callback = callback === undefined ? null : callback;
280
+ if (callback !== null) {
281
+ if (__DEV__) {
282
+ if (typeof callback !== 'function') {
283
+ console.error(
284
+ 'render(...): Expected the last optional `callback` argument to be a ' +
285
+ 'function. Instead received: %s.',
286
+ callback,
287
+ );
288
+ }
289
+ }
290
+ update.callback = callback;
291
+ }
292
+
293
+ enqueueUpdate(current, update);
294
+ scheduleUpdateOnFiber(current, expirationTime);
295
+
296
+ return expirationTime;
297
+}
298
+
299
+export {
300
+ batchedEventUpdates,
301
+ batchedUpdates,
302
+ unbatchedUpdates,
303
+ deferredUpdates,
304
+ syncUpdates,
305
+ discreteUpdates,
306
+ flushDiscreteUpdates,
307
+ flushControlled,
308
+ flushSync,
309
+ flushPassiveEffects,
310
+ IsThisRendererActing,
311
+};
312
+
313
+export function getPublicRootInstance(
314
+ container: OpaqueRoot,
315
+): React$Component<any, any> | PublicInstance | null {
316
+ const containerFiber = container.current;
317
+ if (!containerFiber.child) {
318
+ return null;
319
+ }
320
+ switch (containerFiber.child.tag) {
321
+ case HostComponent:
322
+ return getPublicInstance(containerFiber.child.stateNode);
323
+ default:
324
+ return containerFiber.child.stateNode;
325
+ }
326
+}
327
+
328
+export function attemptSynchronousHydration(fiber: Fiber): void {
329
+ switch (fiber.tag) {
330
+ case HostRoot:
331
+ const root: FiberRoot = fiber.stateNode;
332
+ if (root.hydrate) {
333
+ // Flush the first scheduled "update".
334
+ flushRoot(root, root.firstPendingTime);
335
+ }
336
+ break;
337
+ case SuspenseComponent:
338
+ flushSync(() => scheduleUpdateOnFiber(fiber, Sync));
339
+ // If we're still blocked after this, we need to increase
340
+ // the priority of any promises resolving within this
341
+ // boundary so that they next attempt also has higher pri.
342
+ const retryExpTime = computeInteractiveExpiration(
343
+ requestCurrentTimeForUpdate(),
344
+ );
345
+ markRetryTimeIfNotHydrated(fiber, retryExpTime);
346
+ break;
347
+ }
348
+}
349
+
350
+function markRetryTimeImpl(fiber: Fiber, retryTime: ExpirationTime) {
351
+ const suspenseState: null | SuspenseState = fiber.memoizedState;
352
+ if (suspenseState !== null && suspenseState.dehydrated !== null) {
353
+ if (suspenseState.retryTime < retryTime) {
354
+ suspenseState.retryTime = retryTime;
355
+ }
356
+ }
357
+}
358
+
359
+// Increases the priority of thennables when they resolve within this boundary.
360
+function markRetryTimeIfNotHydrated(fiber: Fiber, retryTime: ExpirationTime) {
361
+ markRetryTimeImpl(fiber, retryTime);
362
+ const alternate = fiber.alternate;
363
+ if (alternate) {
364
+ markRetryTimeImpl(alternate, retryTime);
365
+ }
366
+}
367
+
368
+export function attemptUserBlockingHydration(fiber: Fiber): void {
369
+ if (fiber.tag !== SuspenseComponent) {
370
+ // We ignore HostRoots here because we can't increase
371
+ // their priority and they should not suspend on I/O,
372
+ // since you have to wrap anything that might suspend in
373
+ // Suspense.
374
+ return;
375
+ }
376
+ const expTime = computeInteractiveExpiration(requestCurrentTimeForUpdate());
377
+ scheduleUpdateOnFiber(fiber, expTime);
378
+ markRetryTimeIfNotHydrated(fiber, expTime);
379
+}
380
+
381
+export function attemptContinuousHydration(fiber: Fiber): void {
382
+ if (fiber.tag !== SuspenseComponent) {
383
+ // We ignore HostRoots here because we can't increase
384
+ // their priority and they should not suspend on I/O,
385
+ // since you have to wrap anything that might suspend in
386
+ // Suspense.
387
+ return;
388
+ }
389
+ scheduleUpdateOnFiber(fiber, ContinuousHydration);
390
+ markRetryTimeIfNotHydrated(fiber, ContinuousHydration);
391
+}
392
+
393
+export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
394
+ if (fiber.tag !== SuspenseComponent) {
395
+ // We ignore HostRoots here because we can't increase
396
+ // their priority other than synchronously flush it.
397
+ return;
398
+ }
399
+ const currentTime = requestCurrentTimeForUpdate();
400
+ const expTime = computeExpirationForFiber(currentTime, fiber, null);
401
+ scheduleUpdateOnFiber(fiber, expTime);
402
+ markRetryTimeIfNotHydrated(fiber, expTime);
403
+}
404
+
405
+export {findHostInstance};
406
+
407
+export {findHostInstanceWithWarning};
408
+
409
+export function findHostInstanceWithNoPortals(
410
+ fiber: Fiber,
411
+): PublicInstance | null {
412
+ const hostFiber = findCurrentHostFiberWithNoPortals(fiber);
413
+ if (hostFiber === null) {
414
+ return null;
415
+ }
416
+ if (hostFiber.tag === FundamentalComponent) {
417
+ return hostFiber.stateNode.instance;
418
+ }
419
+ return hostFiber.stateNode;
420
+}
421
+
422
+let shouldSuspendImpl = fiber => false;
423
+
424
+export function shouldSuspend(fiber: Fiber): boolean {
425
+ return shouldSuspendImpl(fiber);
426
+}
427
+
428
+let overrideHookState = null;
429
+let overrideProps = null;
430
+let scheduleUpdate = null;
431
+let setSuspenseHandler = null;
432
+
433
+if (__DEV__) {
434
+ const copyWithSetImpl = (
435
+ obj: Object | Array<any>,
436
+ path: Array<string | number>,
437
+ idx: number,
438
+ value: any,
439
+ ) => {
440
+ if (idx >= path.length) {
441
+ return value;
442
+ }
443
+ const key = path[idx];
444
+ const updated = Array.isArray(obj) ? obj.slice() : {...obj};
445
+ // $FlowFixMe number or string is fine here
446
+ updated[key] = copyWithSetImpl(obj[key], path, idx + 1, value);
447
+ return updated;
448
+ };
449
+
450
+ const copyWithSet = (
451
+ obj: Object | Array<any>,
452
+ path: Array<string | number>,
453
+ value: any,
454
+ ): Object | Array<any> => {
455
+ return copyWithSetImpl(obj, path, 0, value);
456
+ };
457
+
458
+ // Support DevTools editable values for useState and useReducer.
459
+ overrideHookState = (
460
+ fiber: Fiber,
461
+ id: number,
462
+ path: Array<string | number>,
463
+ value: any,
464
+ ) => {
465
+ // For now, the "id" of stateful hooks is just the stateful hook index.
466
+ // This may change in the future with e.g. nested hooks.
467
+ let currentHook = fiber.memoizedState;
468
+ while (currentHook !== null && id > 0) {
469
+ currentHook = currentHook.next;
470
+ id--;
471
+ }
472
+ if (currentHook !== null) {
473
+ const newState = copyWithSet(currentHook.memoizedState, path, value);
474
+ currentHook.memoizedState = newState;
475
+ currentHook.baseState = newState;
476
+
477
+ // We aren't actually adding an update to the queue,
478
+ // because there is no update we can add for useReducer hooks that won't trigger an error.
479
+ // (There's no appropriate action type for DevTools overrides.)
480
+ // As a result though, React will see the scheduled update as a noop and bailout.
481
+ // Shallow cloning props works as a workaround for now to bypass the bailout check.
482
+ fiber.memoizedProps = {...fiber.memoizedProps};
483
+
484
+ scheduleUpdateOnFiber(fiber, Sync);
485
+ }
486
+ };
487
+
488
+ // Support DevTools props for function components, forwardRef, memo, host components, etc.
489
+ overrideProps = (fiber: Fiber, path: Array<string | number>, value: any) => {
490
+ fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value);
491
+ if (fiber.alternate) {
492
+ fiber.alternate.pendingProps = fiber.pendingProps;
493
+ }
494
+ scheduleUpdateOnFiber(fiber, Sync);
495
+ };
496
+
497
+ scheduleUpdate = (fiber: Fiber) => {
498
+ scheduleUpdateOnFiber(fiber, Sync);
499
+ };
500
+
501
+ setSuspenseHandler = (newShouldSuspendImpl: Fiber => boolean) => {
502
+ shouldSuspendImpl = newShouldSuspendImpl;
503
+ };
504
+}
505
+
506
+function findHostInstanceByFiber(fiber: Fiber): Instance | TextInstance | null {
507
+ const hostFiber = findCurrentHostFiber(fiber);
508
+ if (hostFiber === null) {
509
+ return null;
510
+ }
511
+ return hostFiber.stateNode;
512
+}
513
+
514
+function emptyFindFiberByHostInstance(
515
+ instance: Instance | TextInstance,
516
+): Fiber | null {
517
+ return null;
518
+}
519
+
520
+function getCurrentFiberForDevTools() {
521
+ return ReactCurrentFiberCurrent;
522
+}
523
+
524
+export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
525
+ const {findFiberByHostInstance} = devToolsConfig;
526
+ const {ReactCurrentDispatcher} = ReactSharedInternals;
527
+
528
+ return injectInternals({
529
+ bundleType: devToolsConfig.bundleType,
530
+ version: devToolsConfig.version,
531
+ rendererPackageName: devToolsConfig.rendererPackageName,
532
+ rendererConfig: devToolsConfig.rendererConfig,
533
+ overrideHookState,
534
+ overrideProps,
535
+ setSuspenseHandler,
536
+ scheduleUpdate,
537
+ currentDispatcherRef: ReactCurrentDispatcher,
538
+ findHostInstanceByFiber,
539
+ findFiberByHostInstance:
540
+ findFiberByHostInstance || emptyFindFiberByHostInstance,
541
+ // React Refresh
542
+ findHostInstancesForRefresh: __DEV__ ? findHostInstancesForRefresh : null,
543
+ scheduleRefresh: __DEV__ ? scheduleRefresh : null,
544
+ scheduleRoot: __DEV__ ? scheduleRoot : null,
545
+ setRefreshHandler: __DEV__ ? setRefreshHandler : null,
546
+ // Enables DevTools to append owner stacks to error messages in DEV mode.
547
+ getCurrentFiber: __DEV__ ? getCurrentFiberForDevTools : null,
548
+ });
549
+}
550
+
551
+const {IsSomeRendererActing} = ReactSharedInternals;
552
+const isSchedulerMocked =
553
+ typeof Scheduler.unstable_flushAllWithoutAsserting === 'function';
554
+const flushWork =
555
+ Scheduler.unstable_flushAllWithoutAsserting ||
556
+ function() {
557
+ let didFlushWork = false;
558
+ while (flushPassiveEffects()) {
559
+ didFlushWork = true;
560
+ }
561
+
562
+ return didFlushWork;
563
+ };
564
+
565
+function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
566
+ try {
567
+ flushWork();
568
+ enqueueTask(() => {
569
+ if (flushWork()) {
570
+ flushWorkAndMicroTasks(onDone);
571
+ } else {
572
+ onDone();
573
+ }
574
+ });
575
+ } catch (err) {
576
+ onDone(err);
577
+ }
578
+}
579
+
580
+// we track the 'depth' of the act() calls with this counter,
581
+// so we can tell if any async act() calls try to run in parallel.
582
+
583
+let actingUpdatesScopeDepth = 0;
584
+let didWarnAboutUsingActInProd = false;
585
+
586
+// eslint-disable-next-line no-inner-declarations
587
+export function act(callback: () => Thenable<mixed>): Thenable<void> {
588
+ if (!__DEV__) {
589
+ if (didWarnAboutUsingActInProd === false) {
590
+ didWarnAboutUsingActInProd = true;
591
+ // eslint-disable-next-line react-internal/no-production-logging
592
+ console.error(
593
+ 'act(...) is not supported in production builds of React, and might not behave as expected.',
594
+ );
595
+ }
596
+ }
597
+
598
+ const previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
599
+ actingUpdatesScopeDepth++;
600
+
601
+ const previousIsSomeRendererActing = IsSomeRendererActing.current;
602
+ const previousIsThisRendererActing = IsThisRendererActing.current;
603
+ IsSomeRendererActing.current = true;
604
+ IsThisRendererActing.current = true;
605
+
606
+ function onDone() {
607
+ actingUpdatesScopeDepth--;
608
+ IsSomeRendererActing.current = previousIsSomeRendererActing;
609
+ IsThisRendererActing.current = previousIsThisRendererActing;
610
+ if (__DEV__) {
611
+ if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
612
+ // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
613
+ console.error(
614
+ 'You seem to have overlapping act() calls, this is not supported. ' +
615
+ 'Be sure to await previous act() calls before making a new one. ',
616
+ );
617
+ }
618
+ }
619
+ }
620
+
621
+ let result;
622
+ try {
623
+ result = batchedUpdates(callback);
624
+ } catch (error) {
625
+ // on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth
626
+ onDone();
627
+ throw error;
628
+ }
629
+
630
+ if (
631
+ result !== null &&
632
+ typeof result === 'object' &&
633
+ typeof result.then === 'function'
634
+ ) {
635
+ // setup a boolean that gets set to true only
636
+ // once this act() call is await-ed
637
+ let called = false;
638
+ if (__DEV__) {
639
+ if (typeof Promise !== 'undefined') {
640
+ //eslint-disable-next-line no-undef
641
+ Promise.resolve()
642
+ .then(() => {})
643
+ .then(() => {
644
+ if (called === false) {
645
+ console.error(
646
+ 'You called act(async () => ...) without await. ' +
647
+ 'This could lead to unexpected testing behaviour, interleaving multiple act ' +
648
+ 'calls and mixing their scopes. You should - await act(async () => ...);',
649
+ );
650
+ }
651
+ });
652
+ }
653
+ }
654
+
655
+ // in the async case, the returned thenable runs the callback, flushes
656
+ // effects and microtasks in a loop until flushPassiveEffects() === false,
657
+ // and cleans up
658
+ return {
659
+ then(resolve, reject) {
660
+ called = true;
661
+ result.then(
662
+ () => {
663
+ if (
664
+ actingUpdatesScopeDepth > 1 ||
665
+ (isSchedulerMocked === true &&
666
+ previousIsSomeRendererActing === true)
667
+ ) {
668
+ onDone();
669
+ resolve();
670
+ return;
671
+ }
672
+ // we're about to exit the act() scope,
673
+ // now's the time to flush tasks/effects
674
+ flushWorkAndMicroTasks((err: ?Error) => {
675
+ onDone();
676
+ if (err) {
677
+ reject(err);
678
+ } else {
679
+ resolve();
680
+ }
681
+ });
682
+ },
683
+ err => {
684
+ onDone();
685
+ reject(err);
686
+ },
687
+ );
688
+ },
689
+ };
690
+ } else {
691
+ if (__DEV__) {
692
+ if (result !== undefined) {
693
+ console.error(
694
+ 'The callback passed to act(...) function ' +
695
+ 'must return undefined, or a Promise. You returned %s',
696
+ result,
697
+ );
698
+ }
699
+ }
700
+
701
+ // flush effects until none remain, and cleanup
702
+ try {
703
+ if (
704
+ actingUpdatesScopeDepth === 1 &&
705
+ (isSchedulerMocked === false || previousIsSomeRendererActing === false)
706
+ ) {
707
+ // we're about to exit the act() scope,
708
+ // now's the time to flush effects
709
+ flushWork();
710
+ }
711
+ onDone();
712
+ } catch (err) {
713
+ onDone();
714
+ throw err;
715
+ }
716
+
717
+ // in the sync case, the returned thenable only warns *if* await-ed
718
+ return {
719
+ then(resolve) {
720
+ if (__DEV__) {
721
+ console.error(
722
+ 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
723
+ );
724
+ }
725
+ resolve();
726
+ },
727
+ };
728
+ }
729
+}
packages/react-reconciler/src/ReactFiberRoot.new.js
new
+197
@@ -0,0 +1,197 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {FiberRoot, SuspenseHydrationCallbacks} from './ReactInternalTypes';
11
+import type {ExpirationTime} from './ReactFiberExpirationTime';
12
+import type {RootTag} from './ReactRootTags';
13
+
14
+import {noTimeout} from './ReactFiberHostConfig';
15
+import {createHostRootFiber} from './ReactFiber.new';
16
+import {NoWork} from './ReactFiberExpirationTime';
17
+import {
18
+ enableSchedulerTracing,
19
+ enableSuspenseCallback,
20
+} from 'shared/ReactFeatureFlags';
21
+import {unstable_getThreadID} from 'scheduler/tracing';
22
+import {NoPriority} from './SchedulerWithReactIntegration.new';
23
+import {initializeUpdateQueue} from './ReactUpdateQueue.new';
24
+import {clearPendingUpdates as clearPendingMutableSourceUpdates} from './ReactMutableSource.new';
25
+
26
+function FiberRootNode(containerInfo, tag, hydrate) {
27
+ this.tag = tag;
28
+ this.current = null;
29
+ this.containerInfo = containerInfo;
30
+ this.pendingChildren = null;
31
+ this.pingCache = null;
32
+ this.finishedExpirationTime = NoWork;
33
+ this.finishedWork = null;
34
+ this.timeoutHandle = noTimeout;
35
+ this.context = null;
36
+ this.pendingContext = null;
37
+ this.hydrate = hydrate;
38
+ this.callbackNode = null;
39
+ this.callbackPriority = NoPriority;
40
+ this.firstPendingTime = NoWork;
41
+ this.lastPendingTime = NoWork;
42
+ this.firstSuspendedTime = NoWork;
43
+ this.lastSuspendedTime = NoWork;
44
+ this.nextKnownPendingLevel = NoWork;
45
+ this.lastPingedTime = NoWork;
46
+ this.lastExpiredTime = NoWork;
47
+ this.mutableSourceFirstPendingUpdateTime = NoWork;
48
+ this.mutableSourceLastPendingUpdateTime = NoWork;
49
+
50
+ if (enableSchedulerTracing) {
51
+ this.interactionThreadID = unstable_getThreadID();
52
+ this.memoizedInteractions = new Set();
53
+ this.pendingInteractionMap = new Map();
54
+ }
55
+ if (enableSuspenseCallback) {
56
+ this.hydrationCallbacks = null;
57
+ }
58
+}
59
+
60
+export function createFiberRoot(
61
+ containerInfo: any,
62
+ tag: RootTag,
63
+ hydrate: boolean,
64
+ hydrationCallbacks: null | SuspenseHydrationCallbacks,
65
+): FiberRoot {
66
+ const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
67
+ if (enableSuspenseCallback) {
68
+ root.hydrationCallbacks = hydrationCallbacks;
69
+ }
70
+
71
+ // Cyclic construction. This cheats the type system right now because
72
+ // stateNode is any.
73
+ const uninitializedFiber = createHostRootFiber(tag);
74
+ root.current = uninitializedFiber;
75
+ uninitializedFiber.stateNode = root;
76
+
77
+ initializeUpdateQueue(uninitializedFiber);
78
+
79
+ return root;
80
+}
81
+
82
+export function isRootSuspendedAtTime(
83
+ root: FiberRoot,
84
+ expirationTime: ExpirationTime,
85
+): boolean {
86
+ const firstSuspendedTime = root.firstSuspendedTime;
87
+ const lastSuspendedTime = root.lastSuspendedTime;
88
+ return (
89
+ firstSuspendedTime !== NoWork &&
90
+ firstSuspendedTime >= expirationTime &&
91
+ lastSuspendedTime <= expirationTime
92
+ );
93
+}
94
+
95
+export function markRootSuspendedAtTime(
96
+ root: FiberRoot,
97
+ expirationTime: ExpirationTime,
98
+): void {
99
+ const firstSuspendedTime = root.firstSuspendedTime;
100
+ const lastSuspendedTime = root.lastSuspendedTime;
101
+ if (firstSuspendedTime < expirationTime) {
102
+ root.firstSuspendedTime = expirationTime;
103
+ }
104
+ if (lastSuspendedTime > expirationTime || firstSuspendedTime === NoWork) {
105
+ root.lastSuspendedTime = expirationTime;
106
+ }
107
+
108
+ if (expirationTime <= root.lastPingedTime) {
109
+ root.lastPingedTime = NoWork;
110
+ }
111
+
112
+ if (expirationTime <= root.lastExpiredTime) {
113
+ root.lastExpiredTime = NoWork;
114
+ }
115
+}
116
+
117
+export function markRootUpdatedAtTime(
118
+ root: FiberRoot,
119
+ expirationTime: ExpirationTime,
120
+): void {
121
+ // Update the range of pending times
122
+ const firstPendingTime = root.firstPendingTime;
123
+ if (expirationTime > firstPendingTime) {
124
+ root.firstPendingTime = expirationTime;
125
+ }
126
+ const lastPendingTime = root.lastPendingTime;
127
+ if (lastPendingTime === NoWork || expirationTime < lastPendingTime) {
128
+ root.lastPendingTime = expirationTime;
129
+ }
130
+
131
+ // Update the range of suspended times. Treat everything lower priority or
132
+ // equal to this update as unsuspended.
133
+ const firstSuspendedTime = root.firstSuspendedTime;
134
+ if (firstSuspendedTime !== NoWork) {
135
+ if (expirationTime >= firstSuspendedTime) {
136
+ // The entire suspended range is now unsuspended.
137
+ root.firstSuspendedTime = root.lastSuspendedTime = root.nextKnownPendingLevel = NoWork;
138
+ } else if (expirationTime >= root.lastSuspendedTime) {
139
+ root.lastSuspendedTime = expirationTime + 1;
140
+ }
141
+
142
+ // This is a pending level. Check if it's higher priority than the next
143
+ // known pending level.
144
+ if (expirationTime > root.nextKnownPendingLevel) {
145
+ root.nextKnownPendingLevel = expirationTime;
146
+ }
147
+ }
148
+}
149
+
150
+export function markRootFinishedAtTime(
151
+ root: FiberRoot,
152
+ finishedExpirationTime: ExpirationTime,
153
+ remainingExpirationTime: ExpirationTime,
154
+): void {
155
+ // Update the range of pending times
156
+ root.firstPendingTime = remainingExpirationTime;
157
+ if (remainingExpirationTime < root.lastPendingTime) {
158
+ // This usually means we've finished all the work, but it can also happen
159
+ // when something gets downprioritized during render, like a hidden tree.
160
+ root.lastPendingTime = remainingExpirationTime;
161
+ }
162
+
163
+ // Update the range of suspended times. Treat everything higher priority or
164
+ // equal to this update as unsuspended.
165
+ if (finishedExpirationTime <= root.lastSuspendedTime) {
166
+ // The entire suspended range is now unsuspended.
167
+ root.firstSuspendedTime = root.lastSuspendedTime = root.nextKnownPendingLevel = NoWork;
168
+ } else if (finishedExpirationTime <= root.firstSuspendedTime) {
169
+ // Part of the suspended range is now unsuspended. Narrow the range to
170
+ // include everything between the unsuspended time (non-inclusive) and the
171
+ // last suspended time.
172
+ root.firstSuspendedTime = finishedExpirationTime - 1;
173
+ }
174
+
175
+ if (finishedExpirationTime <= root.lastPingedTime) {
176
+ // Clear the pinged time
177
+ root.lastPingedTime = NoWork;
178
+ }
179
+
180
+ if (finishedExpirationTime <= root.lastExpiredTime) {
181
+ // Clear the expired time
182
+ root.lastExpiredTime = NoWork;
183
+ }
184
+
185
+ // Clear any pending updates that were just processed.
186
+ clearPendingMutableSourceUpdates(root, finishedExpirationTime);
187
+}
188
+
189
+export function markRootExpiredAtTime(
190
+ root: FiberRoot,
191
+ expirationTime: ExpirationTime,
192
+): void {
193
+ const lastExpiredTime = root.lastExpiredTime;
194
+ if (lastExpiredTime === NoWork || lastExpiredTime > expirationTime) {
195
+ root.lastExpiredTime = expirationTime;
196
+ }
197
+}
packages/react-reconciler/src/ReactFiberScope.new.js
new
+200
@@ -0,0 +1,200 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactInternalTypes';
11
+import type {
12
+ ReactScope,
13
+ ReactScopeInstance,
14
+ ReactScopeMethods,
15
+ ReactContext,
16
+ ReactScopeQuery,
17
+} from 'shared/ReactTypes';
18
+
19
+import {getPublicInstance, getInstanceFromNode} from './ReactFiberHostConfig';
20
+
21
+import {
22
+ HostComponent,
23
+ SuspenseComponent,
24
+ ScopeComponent,
25
+ ContextProvider,
26
+} from './ReactWorkTags';
27
+import {enableScopeAPI} from 'shared/ReactFeatureFlags';
28
+
29
+function isFiberSuspenseAndTimedOut(fiber: Fiber): boolean {
30
+ const memoizedState = fiber.memoizedState;
31
+ return (
32
+ fiber.tag === SuspenseComponent &&
33
+ memoizedState !== null &&
34
+ memoizedState.dehydrated === null
35
+ );
36
+}
37
+
38
+function getSuspenseFallbackChild(fiber: Fiber): Fiber | null {
39
+ return ((((fiber.child: any): Fiber).sibling: any): Fiber).child;
40
+}
41
+
42
+const emptyObject = {};
43
+
44
+function collectScopedNodes(
45
+ node: Fiber,
46
+ fn: ReactScopeQuery,
47
+ scopedNodes: Array<any>,
48
+): void {
49
+ if (enableScopeAPI) {
50
+ if (node.tag === HostComponent) {
51
+ const {type, memoizedProps, stateNode} = node;
52
+ const instance = getPublicInstance(stateNode);
53
+ if (
54
+ instance !== null &&
55
+ fn(type, memoizedProps || emptyObject, instance) === true
56
+ ) {
57
+ scopedNodes.push(instance);
58
+ }
59
+ }
60
+ let child = node.child;
61
+
62
+ if (isFiberSuspenseAndTimedOut(node)) {
63
+ child = getSuspenseFallbackChild(node);
64
+ }
65
+ if (child !== null) {
66
+ collectScopedNodesFromChildren(child, fn, scopedNodes);
67
+ }
68
+ }
69
+}
70
+
71
+function collectFirstScopedNode(
72
+ node: Fiber,
73
+ fn: ReactScopeQuery,
74
+): null | Object {
75
+ if (enableScopeAPI) {
76
+ if (node.tag === HostComponent) {
77
+ const {type, memoizedProps, stateNode} = node;
78
+ const instance = getPublicInstance(stateNode);
79
+ if (instance !== null && fn(type, memoizedProps, instance) === true) {
80
+ return instance;
81
+ }
82
+ }
83
+ let child = node.child;
84
+
85
+ if (isFiberSuspenseAndTimedOut(node)) {
86
+ child = getSuspenseFallbackChild(node);
87
+ }
88
+ if (child !== null) {
89
+ return collectFirstScopedNodeFromChildren(child, fn);
90
+ }
91
+ }
92
+ return null;
93
+}
94
+
95
+function collectScopedNodesFromChildren(
96
+ startingChild: Fiber,
97
+ fn: ReactScopeQuery,
98
+ scopedNodes: Array<any>,
99
+): void {
100
+ let child = startingChild;
101
+ while (child !== null) {
102
+ collectScopedNodes(child, fn, scopedNodes);
103
+ child = child.sibling;
104
+ }
105
+}
106
+
107
+function collectFirstScopedNodeFromChildren(
108
+ startingChild: Fiber,
109
+ fn: ReactScopeQuery,
110
+): Object | null {
111
+ let child = startingChild;
112
+ while (child !== null) {
113
+ const scopedNode = collectFirstScopedNode(child, fn);
114
+ if (scopedNode !== null) {
115
+ return scopedNode;
116
+ }
117
+ child = child.sibling;
118
+ }
119
+ return null;
120
+}
121
+
122
+function collectNearestContextValues<T>(
123
+ node: Fiber,
124
+ context: ReactContext<T>,
125
+ childContextValues: Array<T>,
126
+): void {
127
+ if (node.tag === ContextProvider && node.type._context === context) {
128
+ const contextValue = node.memoizedProps.value;
129
+ childContextValues.push(contextValue);
130
+ } else {
131
+ let child = node.child;
132
+
133
+ if (isFiberSuspenseAndTimedOut(node)) {
134
+ child = getSuspenseFallbackChild(node);
135
+ }
136
+ if (child !== null) {
137
+ collectNearestChildContextValues(child, context, childContextValues);
138
+ }
139
+ }
140
+}
141
+
142
+function collectNearestChildContextValues<T>(
143
+ startingChild: Fiber | null,
144
+ context: ReactContext<T>,
145
+ childContextValues: Array<T>,
146
+): void {
147
+ let child = startingChild;
148
+ while (child !== null) {
149
+ collectNearestContextValues(child, context, childContextValues);
150
+ child = child.sibling;
151
+ }
152
+}
153
+
154
+export function createScopeMethods(
155
+ scope: ReactScope,
156
+ instance: ReactScopeInstance,
157
+): ReactScopeMethods {
158
+ return {
159
+ DO_NOT_USE_queryAllNodes(fn: ReactScopeQuery): null | Array<Object> {
160
+ const currentFiber = ((instance.fiber: any): Fiber);
161
+ const child = currentFiber.child;
162
+ const scopedNodes = [];
163
+ if (child !== null) {
164
+ collectScopedNodesFromChildren(child, fn, scopedNodes);
165
+ }
166
+ return scopedNodes.length === 0 ? null : scopedNodes;
167
+ },
168
+ DO_NOT_USE_queryFirstNode(fn: ReactScopeQuery): null | Object {
169
+ const currentFiber = ((instance.fiber: any): Fiber);
170
+ const child = currentFiber.child;
171
+ if (child !== null) {
172
+ return collectFirstScopedNodeFromChildren(child, fn);
173
+ }
174
+ return null;
175
+ },
176
+ containsNode(node: Object): boolean {
177
+ let fiber = getInstanceFromNode(node);
178
+ while (fiber !== null) {
179
+ if (
180
+ fiber.tag === ScopeComponent &&
181
+ fiber.type === scope &&
182
+ fiber.stateNode === instance
183
+ ) {
184
+ return true;
185
+ }
186
+ fiber = fiber.return;
187
+ }
188
+ return false;
189
+ },
190
+ getChildContextValues<T>(context: ReactContext<T>): Array<T> {
191
+ const currentFiber = ((instance.fiber: any): Fiber);
192
+ const child = currentFiber.child;
193
+ const childContextValues = [];
194
+ if (child !== null) {
195
+ collectNearestChildContextValues(child, context, childContextValues);
196
+ }
197
+ return childContextValues;
198
+ },
199
+ };
200
+}
packages/react-reconciler/src/ReactFiberStack.new.js
new
+97
@@ -0,0 +1,97 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactInternalTypes';
11
+
12
+export type StackCursor<T> = {|current: T|};
13
+
14
+const valueStack: Array<any> = [];
15
+
16
+let fiberStack: Array<Fiber | null>;
17
+
18
+if (__DEV__) {
19
+ fiberStack = [];
20
+}
21
+
22
+let index = -1;
23
+
24
+function createCursor<T>(defaultValue: T): StackCursor<T> {
25
+ return {
26
+ current: defaultValue,
27
+ };
28
+}
29
+
30
+function isEmpty(): boolean {
31
+ return index === -1;
32
+}
33
+
34
+function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
35
+ if (index < 0) {
36
+ if (__DEV__) {
37
+ console.error('Unexpected pop.');
38
+ }
39
+ return;
40
+ }
41
+
42
+ if (__DEV__) {
43
+ if (fiber !== fiberStack[index]) {
44
+ console.error('Unexpected Fiber popped.');
45
+ }
46
+ }
47
+
48
+ cursor.current = valueStack[index];
49
+
50
+ valueStack[index] = null;
51
+
52
+ if (__DEV__) {
53
+ fiberStack[index] = null;
54
+ }
55
+
56
+ index--;
57
+}
58
+
59
+function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
60
+ index++;
61
+
62
+ valueStack[index] = cursor.current;
63
+
64
+ if (__DEV__) {
65
+ fiberStack[index] = fiber;
66
+ }
67
+
68
+ cursor.current = value;
69
+}
70
+
71
+function checkThatStackIsEmpty() {
72
+ if (__DEV__) {
73
+ if (index !== -1) {
74
+ console.error(
75
+ 'Expected an empty stack. Something was not reset properly.',
76
+ );
77
+ }
78
+ }
79
+}
80
+
81
+function resetStackAfterFatalErrorInDev() {
82
+ if (__DEV__) {
83
+ index = -1;
84
+ valueStack.length = 0;
85
+ fiberStack.length = 0;
86
+ }
87
+}
88
+
89
+export {
90
+ createCursor,
91
+ isEmpty,
92
+ pop,
93
+ push,
94
+ // DEV only:
95
+ checkThatStackIsEmpty,
96
+ resetStackAfterFatalErrorInDev,
97
+};
packages/react-reconciler/src/ReactFiberSuspenseComponent.new.js
new
+139
@@ -0,0 +1,139 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactInternalTypes';
11
+import type {SuspenseInstance} from './ReactFiberHostConfig';
12
+import type {ExpirationTime} from './ReactFiberExpirationTime';
13
+import {SuspenseComponent, SuspenseListComponent} from './ReactWorkTags';
14
+import {NoEffect, DidCapture} from './ReactSideEffectTags';
15
+import {
16
+ isSuspenseInstancePending,
17
+ isSuspenseInstanceFallback,
18
+} from './ReactFiberHostConfig';
19
+
20
+// A null SuspenseState represents an unsuspended normal Suspense boundary.
21
+// A non-null SuspenseState means that it is blocked for one reason or another.
22
+// - A non-null dehydrated field means it's blocked pending hydration.
23
+// - A non-null dehydrated field can use isSuspenseInstancePending or
24
+// isSuspenseInstanceFallback to query the reason for being dehydrated.
25
+// - A null dehydrated field means it's blocked by something suspending and
26
+// we're currently showing a fallback instead.
27
+export type SuspenseState = {|
28
+ // If this boundary is still dehydrated, we store the SuspenseInstance
29
+ // here to indicate that it is dehydrated (flag) and for quick access
30
+ // to check things like isSuspenseInstancePending.
31
+ dehydrated: null | SuspenseInstance,
32
+ // Represents the work that was deprioritized when we committed the fallback.
33
+ // The work outside the boundary already committed at this level, so we cannot
34
+ // unhide the content without including it.
35
+ baseTime: ExpirationTime,
36
+ // Represents the earliest expiration time we should attempt to hydrate
37
+ // a dehydrated boundary at.
38
+ // Never is the default for dehydrated boundaries.
39
+ // NoWork is the default for normal boundaries, which turns into "normal" pri.
40
+ retryTime: ExpirationTime,
41
+|};
42
+
43
+export type SuspenseListTailMode = 'collapsed' | 'hidden' | void;
44
+
45
+export type SuspenseListRenderState = {|
46
+ isBackwards: boolean,
47
+ // The currently rendering tail row.
48
+ rendering: null | Fiber,
49
+ // The absolute time when we started rendering the tail row.
50
+ renderingStartTime: number,
51
+ // The last of the already rendered children.
52
+ last: null | Fiber,
53
+ // Remaining rows on the tail of the list.
54
+ tail: null | Fiber,
55
+ // The absolute time in ms that we'll expire the tail rendering.
56
+ tailExpiration: number,
57
+ // Tail insertions setting.
58
+ tailMode: SuspenseListTailMode,
59
+ // Last Effect before we rendered the "rendering" item.
60
+ // Used to remove new effects added by the rendered item.
61
+ lastEffect: null | Fiber,
62
+|};
63
+
64
+export function shouldCaptureSuspense(
65
+ workInProgress: Fiber,
66
+ hasInvisibleParent: boolean,
67
+): boolean {
68
+ // If it was the primary children that just suspended, capture and render the
69
+ // fallback. Otherwise, don't capture and bubble to the next boundary.
70
+ const nextState: SuspenseState | null = workInProgress.memoizedState;
71
+ if (nextState !== null) {
72
+ if (nextState.dehydrated !== null) {
73
+ // A dehydrated boundary always captures.
74
+ return true;
75
+ }
76
+ return false;
77
+ }
78
+ const props = workInProgress.memoizedProps;
79
+ // In order to capture, the Suspense component must have a fallback prop.
80
+ if (props.fallback === undefined) {
81
+ return false;
82
+ }
83
+ // Regular boundaries always capture.
84
+ if (props.unstable_avoidThisFallback !== true) {
85
+ return true;
86
+ }
87
+ // If it's a boundary we should avoid, then we prefer to bubble up to the
88
+ // parent boundary if it is currently invisible.
89
+ if (hasInvisibleParent) {
90
+ return false;
91
+ }
92
+ // If the parent is not able to handle it, we must handle it.
93
+ return true;
94
+}
95
+
96
+export function findFirstSuspended(row: Fiber): null | Fiber {
97
+ let node = row;
98
+ while (node !== null) {
99
+ if (node.tag === SuspenseComponent) {
100
+ const state: SuspenseState | null = node.memoizedState;
101
+ if (state !== null) {
102
+ const dehydrated: null | SuspenseInstance = state.dehydrated;
103
+ if (
104
+ dehydrated === null ||
105
+ isSuspenseInstancePending(dehydrated) ||
106
+ isSuspenseInstanceFallback(dehydrated)
107
+ ) {
108
+ return node;
109
+ }
110
+ }
111
+ } else if (
112
+ node.tag === SuspenseListComponent &&
113
+ // revealOrder undefined can't be trusted because it don't
114
+ // keep track of whether it suspended or not.
115
+ node.memoizedProps.revealOrder !== undefined
116
+ ) {
117
+ const didSuspend = (node.effectTag & DidCapture) !== NoEffect;
118
+ if (didSuspend) {
119
+ return node;
120
+ }
121
+ } else if (node.child !== null) {
122
+ node.child.return = node;
123
+ node = node.child;
124
+ continue;
125
+ }
126
+ if (node === row) {
127
+ return null;
128
+ }
129
+ while (node.sibling === null) {
130
+ if (node.return === null || node.return === row) {
131
+ return null;
132
+ }
133
+ node = node.return;
134
+ }
135
+ node.sibling.return = node.return;
136
+ node = node.sibling;
137
+ }
138
+ return null;
139
+}
packages/react-reconciler/src/ReactFiberSuspenseContext.new.js
new
+83
@@ -0,0 +1,83 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactInternalTypes';
11
+import type {StackCursor} from './ReactFiberStack.new';
12
+
13
+import {createCursor, push, pop} from './ReactFiberStack.new';
14
+
15
+export opaque type SuspenseContext = number;
16
+export opaque type SubtreeSuspenseContext: SuspenseContext = number;
17
+export opaque type ShallowSuspenseContext: SuspenseContext = number;
18
+
19
+const DefaultSuspenseContext: SuspenseContext = 0b00;
20
+
21
+// The Suspense Context is split into two parts. The lower bits is
22
+// inherited deeply down the subtree. The upper bits only affect
23
+// this immediate suspense boundary and gets reset each new
24
+// boundary or suspense list.
25
+const SubtreeSuspenseContextMask: SuspenseContext = 0b01;
26
+
27
+// Subtree Flags:
28
+
29
+// InvisibleParentSuspenseContext indicates that one of our parent Suspense
30
+// boundaries is not currently showing visible main content.
31
+// Either because it is already showing a fallback or is not mounted at all.
32
+// We can use this to determine if it is desirable to trigger a fallback at
33
+// the parent. If not, then we might need to trigger undesirable boundaries
34
+// and/or suspend the commit to avoid hiding the parent content.
35
+export const InvisibleParentSuspenseContext: SubtreeSuspenseContext = 0b01;
36
+
37
+// Shallow Flags:
38
+
39
+// ForceSuspenseFallback can be used by SuspenseList to force newly added
40
+// items into their fallback state during one of the render passes.
41
+export const ForceSuspenseFallback: ShallowSuspenseContext = 0b10;
42
+
43
+export const suspenseStackCursor: StackCursor<SuspenseContext> = createCursor(
44
+ DefaultSuspenseContext,
45
+);
46
+
47
+export function hasSuspenseContext(
48
+ parentContext: SuspenseContext,
49
+ flag: SuspenseContext,
50
+): boolean {
51
+ return (parentContext & flag) !== 0;
52
+}
53
+
54
+export function setDefaultShallowSuspenseContext(
55
+ parentContext: SuspenseContext,
56
+): SuspenseContext {
57
+ return parentContext & SubtreeSuspenseContextMask;
58
+}
59
+
60
+export function setShallowSuspenseContext(
61
+ parentContext: SuspenseContext,
62
+ shallowContext: ShallowSuspenseContext,
63
+): SuspenseContext {
64
+ return (parentContext & SubtreeSuspenseContextMask) | shallowContext;
65
+}
66
+
67
+export function addSubtreeSuspenseContext(
68
+ parentContext: SuspenseContext,
69
+ subtreeContext: SubtreeSuspenseContext,
70
+): SuspenseContext {
71
+ return parentContext | subtreeContext;
72
+}
73
+
74
+export function pushSuspenseContext(
75
+ fiber: Fiber,
76
+ newContext: SuspenseContext,
77
+): void {
78
+ push(suspenseStackCursor, newContext, fiber);
79
+}
80
+
81
+export function popSuspenseContext(fiber: Fiber): void {
82
+ pop(suspenseStackCursor, fiber);
83
+}
packages/react-reconciler/src/ReactFiberThrow.new.js
new
+393
@@ -0,0 +1,393 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactInternalTypes';
11
+import type {FiberRoot} from './ReactInternalTypes';
12
+import type {ExpirationTime} from './ReactFiberExpirationTime';
13
+import type {CapturedValue} from './ReactCapturedValue';
14
+import type {Update} from './ReactUpdateQueue.new';
15
+import type {Wakeable} from 'shared/ReactTypes';
16
+import type {SuspenseContext} from './ReactFiberSuspenseContext.new';
17
+
18
+import getComponentName from 'shared/getComponentName';
19
+import {
20
+ ClassComponent,
21
+ HostRoot,
22
+ SuspenseComponent,
23
+ IncompleteClassComponent,
24
+} from './ReactWorkTags';
25
+import {
26
+ DidCapture,
27
+ Incomplete,
28
+ NoEffect,
29
+ ShouldCapture,
30
+ LifecycleEffectMask,
31
+} from './ReactSideEffectTags';
32
+import {NoMode, BlockingMode} from './ReactTypeOfMode';
33
+import {shouldCaptureSuspense} from './ReactFiberSuspenseComponent.new';
34
+
35
+import {createCapturedValue} from './ReactCapturedValue';
36
+import {
37
+ enqueueCapturedUpdate,
38
+ createUpdate,
39
+ CaptureUpdate,
40
+ ForceUpdate,
41
+ enqueueUpdate,
42
+} from './ReactUpdateQueue.new';
43
+import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
44
+import {markFailedErrorBoundaryForHotReloading} from './ReactFiberHotReloading.new';
45
+import {
46
+ suspenseStackCursor,
47
+ InvisibleParentSuspenseContext,
48
+ hasSuspenseContext,
49
+} from './ReactFiberSuspenseContext.new';
50
+import {
51
+ renderDidError,
52
+ onUncaughtError,
53
+ markLegacyErrorBoundaryAsFailed,
54
+ isAlreadyFailedLegacyErrorBoundary,
55
+ pingSuspendedRoot,
56
+} from './ReactFiberWorkLoop.new';
57
+import {logCapturedError} from './ReactFiberErrorLogger';
58
+
59
+import {Sync} from './ReactFiberExpirationTime';
60
+
61
+const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
62
+
63
+function createRootErrorUpdate(
64
+ fiber: Fiber,
65
+ errorInfo: CapturedValue<mixed>,
66
+ expirationTime: ExpirationTime,
67
+): Update<mixed> {
68
+ const update = createUpdate(expirationTime, null);
69
+ // Unmount the root by rendering null.
70
+ update.tag = CaptureUpdate;
71
+ // Caution: React DevTools currently depends on this property
72
+ // being called "element".
73
+ update.payload = {element: null};
74
+ const error = errorInfo.value;
75
+ update.callback = () => {
76
+ onUncaughtError(error);
77
+ logCapturedError(fiber, errorInfo);
78
+ };
79
+ return update;
80
+}
81
+
82
+function createClassErrorUpdate(
83
+ fiber: Fiber,
84
+ errorInfo: CapturedValue<mixed>,
85
+ expirationTime: ExpirationTime,
86
+): Update<mixed> {
87
+ const update = createUpdate(expirationTime, null);
88
+ update.tag = CaptureUpdate;
89
+ const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
90
+ if (typeof getDerivedStateFromError === 'function') {
91
+ const error = errorInfo.value;
92
+ update.payload = () => {
93
+ logCapturedError(fiber, errorInfo);
94
+ return getDerivedStateFromError(error);
95
+ };
96
+ }
97
+
98
+ const inst = fiber.stateNode;
99
+ if (inst !== null && typeof inst.componentDidCatch === 'function') {
100
+ update.callback = function callback() {
101
+ if (__DEV__) {
102
+ markFailedErrorBoundaryForHotReloading(fiber);
103
+ }
104
+ if (typeof getDerivedStateFromError !== 'function') {
105
+ // To preserve the preexisting retry behavior of error boundaries,
106
+ // we keep track of which ones already failed during this batch.
107
+ // This gets reset before we yield back to the browser.
108
+ // TODO: Warn in strict mode if getDerivedStateFromError is
109
+ // not defined.
110
+ markLegacyErrorBoundaryAsFailed(this);
111
+
112
+ // Only log here if componentDidCatch is the only error boundary method defined
113
+ logCapturedError(fiber, errorInfo);
114
+ }
115
+ const error = errorInfo.value;
116
+ const stack = errorInfo.stack;
117
+ this.componentDidCatch(error, {
118
+ componentStack: stack !== null ? stack : '',
119
+ });
120
+ if (__DEV__) {
121
+ if (typeof getDerivedStateFromError !== 'function') {
122
+ // If componentDidCatch is the only error boundary method defined,
123
+ // then it needs to call setState to recover from errors.
124
+ // If no state update is scheduled then the boundary will swallow the error.
125
+ if (fiber.expirationTime !== Sync) {
126
+ console.error(
127
+ '%s: Error boundaries should implement getDerivedStateFromError(). ' +
128
+ 'In that method, return a state update to display an error message or fallback UI.',
129
+ getComponentName(fiber.type) || 'Unknown',
130
+ );
131
+ }
132
+ }
133
+ }
134
+ };
135
+ } else if (__DEV__) {
136
+ update.callback = () => {
137
+ markFailedErrorBoundaryForHotReloading(fiber);
138
+ };
139
+ }
140
+ return update;
141
+}
142
+
143
+function attachPingListener(
144
+ root: FiberRoot,
145
+ renderExpirationTime: ExpirationTime,
146
+ wakeable: Wakeable,
147
+) {
148
+ // Attach a listener to the promise to "ping" the root and retry. But
149
+ // only if one does not already exist for the current render expiration
150
+ // time (which acts like a "thread ID" here).
151
+ let pingCache = root.pingCache;
152
+ let threadIDs;
153
+ if (pingCache === null) {
154
+ pingCache = root.pingCache = new PossiblyWeakMap();
155
+ threadIDs = new Set();
156
+ pingCache.set(wakeable, threadIDs);
157
+ } else {
158
+ threadIDs = pingCache.get(wakeable);
159
+ if (threadIDs === undefined) {
160
+ threadIDs = new Set();
161
+ pingCache.set(wakeable, threadIDs);
162
+ }
163
+ }
164
+ if (!threadIDs.has(renderExpirationTime)) {
165
+ // Memoize using the thread ID to prevent redundant listeners.
166
+ threadIDs.add(renderExpirationTime);
167
+ const ping = pingSuspendedRoot.bind(
168
+ null,
169
+ root,
170
+ wakeable,
171
+ renderExpirationTime,
172
+ );
173
+ wakeable.then(ping, ping);
174
+ }
175
+}
176
+
177
+function throwException(
178
+ root: FiberRoot,
179
+ returnFiber: Fiber,
180
+ sourceFiber: Fiber,
181
+ value: mixed,
182
+ renderExpirationTime: ExpirationTime,
183
+) {
184
+ // The source fiber did not complete.
185
+ sourceFiber.effectTag |= Incomplete;
186
+ // Its effect list is no longer valid.
187
+ sourceFiber.firstEffect = sourceFiber.lastEffect = null;
188
+
189
+ if (
190
+ value !== null &&
191
+ typeof value === 'object' &&
192
+ typeof value.then === 'function'
193
+ ) {
194
+ // This is a wakeable.
195
+ const wakeable: Wakeable = (value: any);
196
+
197
+ if ((sourceFiber.mode & BlockingMode) === NoMode) {
198
+ // Reset the memoizedState to what it was before we attempted
199
+ // to render it.
200
+ const currentSource = sourceFiber.alternate;
201
+ if (currentSource) {
202
+ sourceFiber.updateQueue = currentSource.updateQueue;
203
+ sourceFiber.memoizedState = currentSource.memoizedState;
204
+ sourceFiber.expirationTime = currentSource.expirationTime;
205
+ } else {
206
+ sourceFiber.updateQueue = null;
207
+ sourceFiber.memoizedState = null;
208
+ }
209
+ }
210
+
211
+ const hasInvisibleParentBoundary = hasSuspenseContext(
212
+ suspenseStackCursor.current,
213
+ (InvisibleParentSuspenseContext: SuspenseContext),
214
+ );
215
+
216
+ // Schedule the nearest Suspense to re-render the timed out view.
217
+ let workInProgress = returnFiber;
218
+ do {
219
+ if (
220
+ workInProgress.tag === SuspenseComponent &&
221
+ shouldCaptureSuspense(workInProgress, hasInvisibleParentBoundary)
222
+ ) {
223
+ // Found the nearest boundary.
224
+
225
+ // Stash the promise on the boundary fiber. If the boundary times out, we'll
226
+ // attach another listener to flip the boundary back to its normal state.
227
+ const wakeables: Set<Wakeable> = (workInProgress.updateQueue: any);
228
+ if (wakeables === null) {
229
+ const updateQueue = (new Set(): any);
230
+ updateQueue.add(wakeable);
231
+ workInProgress.updateQueue = updateQueue;
232
+ } else {
233
+ wakeables.add(wakeable);
234
+ }
235
+
236
+ // If the boundary is outside of blocking mode, we should *not*
237
+ // suspend the commit. Pretend as if the suspended component rendered
238
+ // null and keep rendering. In the commit phase, we'll schedule a
239
+ // subsequent synchronous update to re-render the Suspense.
240
+ //
241
+ // Note: It doesn't matter whether the component that suspended was
242
+ // inside a blocking mode tree. If the Suspense is outside of it, we
243
+ // should *not* suspend the commit.
244
+ if ((workInProgress.mode & BlockingMode) === NoMode) {
245
+ workInProgress.effectTag |= DidCapture;
246
+
247
+ // We're going to commit this fiber even though it didn't complete.
248
+ // But we shouldn't call any lifecycle methods or callbacks. Remove
249
+ // all lifecycle effect tags.
250
+ sourceFiber.effectTag &= ~(LifecycleEffectMask | Incomplete);
251
+
252
+ if (sourceFiber.tag === ClassComponent) {
253
+ const currentSourceFiber = sourceFiber.alternate;
254
+ if (currentSourceFiber === null) {
255
+ // This is a new mount. Change the tag so it's not mistaken for a
256
+ // completed class component. For example, we should not call
257
+ // componentWillUnmount if it is deleted.
258
+ sourceFiber.tag = IncompleteClassComponent;
259
+ } else {
260
+ // When we try rendering again, we should not reuse the current fiber,
261
+ // since it's known to be in an inconsistent state. Use a force update to
262
+ // prevent a bail out.
263
+ const update = createUpdate(Sync, null);
264
+ update.tag = ForceUpdate;
265
+ enqueueUpdate(sourceFiber, update);
266
+ }
267
+ }
268
+
269
+ // The source fiber did not complete. Mark it with Sync priority to
270
+ // indicate that it still has pending work.
271
+ sourceFiber.expirationTime = Sync;
272
+
273
+ // Exit without suspending.
274
+ return;
275
+ }
276
+
277
+ // Confirmed that the boundary is in a concurrent mode tree. Continue
278
+ // with the normal suspend path.
279
+ //
280
+ // After this we'll use a set of heuristics to determine whether this
281
+ // render pass will run to completion or restart or "suspend" the commit.
282
+ // The actual logic for this is spread out in different places.
283
+ //
284
+ // This first principle is that if we're going to suspend when we complete
285
+ // a root, then we should also restart if we get an update or ping that
286
+ // might unsuspend it, and vice versa. The only reason to suspend is
287
+ // because you think you might want to restart before committing. However,
288
+ // it doesn't make sense to restart only while in the period we're suspended.
289
+ //
290
+ // Restarting too aggressively is also not good because it starves out any
291
+ // intermediate loading state. So we use heuristics to determine when.
292
+
293
+ // Suspense Heuristics
294
+ //
295
+ // If nothing threw a Promise or all the same fallbacks are already showing,
296
+ // then don't suspend/restart.
297
+ //
298
+ // If this is an initial render of a new tree of Suspense boundaries and
299
+ // those trigger a fallback, then don't suspend/restart. We want to ensure
300
+ // that we can show the initial loading state as quickly as possible.
301
+ //
302
+ // If we hit a "Delayed" case, such as when we'd switch from content back into
303
+ // a fallback, then we should always suspend/restart. SuspenseConfig applies to
304
+ // this case. If none is defined, JND is used instead.
305
+ //
306
+ // If we're already showing a fallback and it gets "retried", allowing us to show
307
+ // another level, but there's still an inner boundary that would show a fallback,
308
+ // then we suspend/restart for 500ms since the last time we showed a fallback
309
+ // anywhere in the tree. This effectively throttles progressive loading into a
310
+ // consistent train of commits. This also gives us an opportunity to restart to
311
+ // get to the completed state slightly earlier.
312
+ //
313
+ // If there's ambiguity due to batching it's resolved in preference of:
314
+ // 1) "delayed", 2) "initial render", 3) "retry".
315
+ //
316
+ // We want to ensure that a "busy" state doesn't get force committed. We want to
317
+ // ensure that new initial loading states can commit as soon as possible.
318
+
319
+ attachPingListener(root, renderExpirationTime, wakeable);
320
+
321
+ workInProgress.effectTag |= ShouldCapture;
322
+ workInProgress.expirationTime = renderExpirationTime;
323
+
324
+ return;
325
+ }
326
+ // This boundary already captured during this render. Continue to the next
327
+ // boundary.
328
+ workInProgress = workInProgress.return;
329
+ } while (workInProgress !== null);
330
+ // No boundary was found. Fallthrough to error mode.
331
+ // TODO: Use invariant so the message is stripped in prod?
332
+ value = new Error(
333
+ (getComponentName(sourceFiber.type) || 'A React component') +
334
+ ' suspended while rendering, but no fallback UI was specified.\n' +
335
+ '\n' +
336
+ 'Add a <Suspense fallback=...> component higher in the tree to ' +
337
+ 'provide a loading indicator or placeholder to display.' +
338
+ getStackByFiberInDevAndProd(sourceFiber),
339
+ );
340
+ }
341
+
342
+ // We didn't find a boundary that could handle this type of exception. Start
343
+ // over and traverse parent path again, this time treating the exception
344
+ // as an error.
345
+ renderDidError();
346
+ value = createCapturedValue(value, sourceFiber);
347
+ let workInProgress = returnFiber;
348
+ do {
349
+ switch (workInProgress.tag) {
350
+ case HostRoot: {
351
+ const errorInfo = value;
352
+ workInProgress.effectTag |= ShouldCapture;
353
+ workInProgress.expirationTime = renderExpirationTime;
354
+ const update = createRootErrorUpdate(
355
+ workInProgress,
356
+ errorInfo,
357
+ renderExpirationTime,
358
+ );
359
+ enqueueCapturedUpdate(workInProgress, update);
360
+ return;
361
+ }
362
+ case ClassComponent:
363
+ // Capture and retry
364
+ const errorInfo = value;
365
+ const ctor = workInProgress.type;
366
+ const instance = workInProgress.stateNode;
367
+ if (
368
+ (workInProgress.effectTag & DidCapture) === NoEffect &&
369
+ (typeof ctor.getDerivedStateFromError === 'function' ||
370
+ (instance !== null &&
371
+ typeof instance.componentDidCatch === 'function' &&
372
+ !isAlreadyFailedLegacyErrorBoundary(instance)))
373
+ ) {
374
+ workInProgress.effectTag |= ShouldCapture;
375
+ workInProgress.expirationTime = renderExpirationTime;
376
+ // Schedule the error boundary to re-render using updated state
377
+ const update = createClassErrorUpdate(
378
+ workInProgress,
379
+ errorInfo,
380
+ renderExpirationTime,
381
+ );
382
+ enqueueCapturedUpdate(workInProgress, update);
383
+ return;
384
+ }
385
+ break;
386
+ default:
387
+ break;
388
+ }
389
+ workInProgress = workInProgress.return;
390
+ } while (workInProgress !== null);
391
+}
392
+
393
+export {throwException, createRootErrorUpdate, createClassErrorUpdate};
packages/react-reconciler/src/ReactFiberUnwindWork.new.js
new
+149
@@ -0,0 +1,149 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactInternalTypes';
11
+import type {ExpirationTime} from './ReactFiberExpirationTime';
12
+import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
13
+
14
+import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.new';
15
+import {
16
+ ClassComponent,
17
+ HostRoot,
18
+ HostComponent,
19
+ HostPortal,
20
+ ContextProvider,
21
+ SuspenseComponent,
22
+ SuspenseListComponent,
23
+} from './ReactWorkTags';
24
+import {DidCapture, NoEffect, ShouldCapture} from './ReactSideEffectTags';
25
+import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';
26
+
27
+import {popHostContainer, popHostContext} from './ReactFiberHostContext.new';
28
+import {popSuspenseContext} from './ReactFiberSuspenseContext.new';
29
+import {resetHydrationState} from './ReactFiberHydrationContext.new';
30
+import {
31
+ isContextProvider as isLegacyContextProvider,
32
+ popContext as popLegacyContext,
33
+ popTopLevelContextObject as popTopLevelLegacyContextObject,
34
+} from './ReactFiberContext.new';
35
+import {popProvider} from './ReactFiberNewContext.new';
36
+
37
+import invariant from 'shared/invariant';
38
+
39
+function unwindWork(
40
+ workInProgress: Fiber,
41
+ renderExpirationTime: ExpirationTime,
42
+) {
43
+ switch (workInProgress.tag) {
44
+ case ClassComponent: {
45
+ const Component = workInProgress.type;
46
+ if (isLegacyContextProvider(Component)) {
47
+ popLegacyContext(workInProgress);
48
+ }
49
+ const effectTag = workInProgress.effectTag;
50
+ if (effectTag & ShouldCapture) {
51
+ workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
52
+ return workInProgress;
53
+ }
54
+ return null;
55
+ }
56
+ case HostRoot: {
57
+ popHostContainer(workInProgress);
58
+ popTopLevelLegacyContextObject(workInProgress);
59
+ resetMutableSourceWorkInProgressVersions();
60
+ const effectTag = workInProgress.effectTag;
61
+ invariant(
62
+ (effectTag & DidCapture) === NoEffect,
63
+ 'The root failed to unmount after an error. This is likely a bug in ' +
64
+ 'React. Please file an issue.',
65
+ );
66
+ workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
67
+ return workInProgress;
68
+ }
69
+ case HostComponent: {
70
+ // TODO: popHydrationState
71
+ popHostContext(workInProgress);
72
+ return null;
73
+ }
74
+ case SuspenseComponent: {
75
+ popSuspenseContext(workInProgress);
76
+ if (enableSuspenseServerRenderer) {
77
+ const suspenseState: null | SuspenseState =
78
+ workInProgress.memoizedState;
79
+ if (suspenseState !== null && suspenseState.dehydrated !== null) {
80
+ invariant(
81
+ workInProgress.alternate !== null,
82
+ 'Threw in newly mounted dehydrated component. This is likely a bug in ' +
83
+ 'React. Please file an issue.',
84
+ );
85
+ resetHydrationState();
86
+ }
87
+ }
88
+ const effectTag = workInProgress.effectTag;
89
+ if (effectTag & ShouldCapture) {
90
+ workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
91
+ // Captured a suspense effect. Re-render the boundary.
92
+ return workInProgress;
93
+ }
94
+ return null;
95
+ }
96
+ case SuspenseListComponent: {
97
+ popSuspenseContext(workInProgress);
98
+ // SuspenseList doesn't actually catch anything. It should've been
99
+ // caught by a nested boundary. If not, it should bubble through.
100
+ return null;
101
+ }
102
+ case HostPortal:
103
+ popHostContainer(workInProgress);
104
+ return null;
105
+ case ContextProvider:
106
+ popProvider(workInProgress);
107
+ return null;
108
+ default:
109
+ return null;
110
+ }
111
+}
112
+
113
+function unwindInterruptedWork(interruptedWork: Fiber) {
114
+ switch (interruptedWork.tag) {
115
+ case ClassComponent: {
116
+ const childContextTypes = interruptedWork.type.childContextTypes;
117
+ if (childContextTypes !== null && childContextTypes !== undefined) {
118
+ popLegacyContext(interruptedWork);
119
+ }
120
+ break;
121
+ }
122
+ case HostRoot: {
123
+ popHostContainer(interruptedWork);
124
+ popTopLevelLegacyContextObject(interruptedWork);
125
+ resetMutableSourceWorkInProgressVersions();
126
+ break;
127
+ }
128
+ case HostComponent: {
129
+ popHostContext(interruptedWork);
130
+ break;
131
+ }
132
+ case HostPortal:
133
+ popHostContainer(interruptedWork);
134
+ break;
135
+ case SuspenseComponent:
136
+ popSuspenseContext(interruptedWork);
137
+ break;
138
+ case SuspenseListComponent:
139
+ popSuspenseContext(interruptedWork);
140
+ break;
141
+ case ContextProvider:
142
+ popProvider(interruptedWork);
143
+ break;
144
+ default:
145
+ break;
146
+ }
147
+}
148
+
149
+export {unwindWork, unwindInterruptedWork};
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
new
+3204
@@ -0,0 +1,3204 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Wakeable} from 'shared/ReactTypes';
11
+import type {Fiber, FiberRoot} from './ReactInternalTypes';
12
+import type {ExpirationTime} from './ReactFiberExpirationTime';
13
+import type {ReactPriorityLevel} from './ReactInternalTypes';
14
+import type {Interaction} from 'scheduler/src/Tracing';
15
+import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
16
+import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
17
+import type {Effect as HookEffect} from './ReactFiberHooks.new';
18
+
19
+import {
20
+ warnAboutDeprecatedLifecycles,
21
+ deferPassiveEffectCleanupDuringUnmount,
22
+ runAllPassiveEffectDestroysBeforeCreates,
23
+ enableSuspenseServerRenderer,
24
+ replayFailedUnitOfWorkWithInvokeGuardedCallback,
25
+ enableProfilerTimer,
26
+ enableProfilerCommitHooks,
27
+ enableSchedulerTracing,
28
+ warnAboutUnmockedScheduler,
29
+ flushSuspenseFallbacksInTests,
30
+ disableSchedulerTimeoutBasedOnReactExpirationTime,
31
+} from 'shared/ReactFeatureFlags';
32
+import ReactSharedInternals from 'shared/ReactSharedInternals';
33
+import invariant from 'shared/invariant';
34
+
35
+import {
36
+ scheduleCallback,
37
+ cancelCallback,
38
+ getCurrentPriorityLevel,
39
+ runWithPriority,
40
+ shouldYield,
41
+ requestPaint,
42
+ now,
43
+ NoPriority,
44
+ ImmediatePriority,
45
+ UserBlockingPriority,
46
+ NormalPriority,
47
+ LowPriority,
48
+ IdlePriority,
49
+ flushSyncCallbackQueue,
50
+ scheduleSyncCallback,
51
+} from './SchedulerWithReactIntegration.new';
52
+
53
+// The scheduler is imported here *only* to detect whether it's been mocked
54
+import * as Scheduler from 'scheduler';
55
+
56
+import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
57
+
58
+import {
59
+ prepareForCommit,
60
+ resetAfterCommit,
61
+ scheduleTimeout,
62
+ cancelTimeout,
63
+ noTimeout,
64
+ warnsIfNotActing,
65
+} from './ReactFiberHostConfig';
66
+
67
+import {
68
+ createWorkInProgress,
69
+ assignFiberPropertiesInDEV,
70
+} from './ReactFiber.new';
71
+import {
72
+ isRootSuspendedAtTime,
73
+ markRootSuspendedAtTime,
74
+ markRootFinishedAtTime,
75
+ markRootUpdatedAtTime,
76
+ markRootExpiredAtTime,
77
+} from './ReactFiberRoot.new';
78
+import {
79
+ NoMode,
80
+ StrictMode,
81
+ ProfileMode,
82
+ BlockingMode,
83
+ ConcurrentMode,
84
+} from './ReactTypeOfMode';
85
+import {
86
+ HostRoot,
87
+ ClassComponent,
88
+ SuspenseComponent,
89
+ SuspenseListComponent,
90
+ FunctionComponent,
91
+ ForwardRef,
92
+ MemoComponent,
93
+ SimpleMemoComponent,
94
+ Block,
95
+} from './ReactWorkTags';
96
+import {LegacyRoot} from './ReactRootTags';
97
+import {
98
+ NoEffect,
99
+ PerformedWork,
100
+ Placement,
101
+ Update,
102
+ PlacementAndUpdate,
103
+ Deletion,
104
+ Ref,
105
+ ContentReset,
106
+ Snapshot,
107
+ Callback,
108
+ Passive,
109
+ Incomplete,
110
+ HostEffectMask,
111
+ Hydrating,
112
+ HydratingAndUpdate,
113
+} from './ReactSideEffectTags';
114
+import {
115
+ NoWork,
116
+ Sync,
117
+ Never,
118
+ msToExpirationTime,
119
+ expirationTimeToMs,
120
+ computeInteractiveExpiration,
121
+ computeAsyncExpiration,
122
+ computeSuspenseExpiration,
123
+ inferPriorityFromExpirationTime,
124
+ LOW_PRIORITY_EXPIRATION,
125
+ Batched,
126
+ Idle,
127
+} from './ReactFiberExpirationTime';
128
+import {beginWork as originalBeginWork} from './ReactFiberBeginWork.new';
129
+import {completeWork} from './ReactFiberCompleteWork.new';
130
+import {unwindWork, unwindInterruptedWork} from './ReactFiberUnwindWork.new';
131
+import {
132
+ throwException,
133
+ createRootErrorUpdate,
134
+ createClassErrorUpdate,
135
+} from './ReactFiberThrow.new';
136
+import {
137
+ commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
138
+ commitLifeCycles as commitLayoutEffectOnFiber,
139
+ commitPassiveHookEffects,
140
+ commitPlacement,
141
+ commitWork,
142
+ commitDeletion,
143
+ commitDetachRef,
144
+ commitAttachRef,
145
+ commitPassiveEffectDurations,
146
+ commitResetTextContent,
147
+} from './ReactFiberCommitWork.new';
148
+import {enqueueUpdate} from './ReactUpdateQueue.new';
149
+import {resetContextDependencies} from './ReactFiberNewContext.new';
150
+import {
151
+ resetHooksAfterThrow,
152
+ ContextOnlyDispatcher,
153
+ getIsUpdatingOpaqueValueInRenderPhaseInDEV,
154
+} from './ReactFiberHooks.new';
155
+import {createCapturedValue} from './ReactCapturedValue';
156
+
157
+import {
158
+ recordCommitTime,
159
+ recordPassiveEffectDuration,
160
+ startPassiveEffectTimer,
161
+ startProfilerTimer,
162
+ stopProfilerTimerIfRunningAndRecordDelta,
163
+} from './ReactProfilerTimer.new';
164
+
165
+// DEV stuff
166
+import getComponentName from 'shared/getComponentName';
167
+import ReactStrictModeWarnings from './ReactStrictModeWarnings.new';
168
+import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
169
+import {
170
+ isRendering as ReactCurrentDebugFiberIsRenderingInDEV,
171
+ resetCurrentFiber as resetCurrentDebugFiberInDEV,
172
+ setCurrentFiber as setCurrentDebugFiberInDEV,
173
+} from './ReactCurrentFiber';
174
+import {
175
+ invokeGuardedCallback,
176
+ hasCaughtError,
177
+ clearCaughtError,
178
+} from 'shared/ReactErrorUtils';
179
+import {onCommitRoot} from './ReactFiberDevToolsHook.new';
180
+
181
+const ceil = Math.ceil;
182
+
183
+const {
184
+ ReactCurrentDispatcher,
185
+ ReactCurrentOwner,
186
+ IsSomeRendererActing,
187
+} = ReactSharedInternals;
188
+
189
+type ExecutionContext = number;
190
+
191
+const NoContext = /* */ 0b000000;
192
+const BatchedContext = /* */ 0b000001;
193
+const EventContext = /* */ 0b000010;
194
+const DiscreteEventContext = /* */ 0b000100;
195
+const LegacyUnbatchedContext = /* */ 0b001000;
196
+const RenderContext = /* */ 0b010000;
197
+const CommitContext = /* */ 0b100000;
198
+
199
+type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5;
200
+const RootIncomplete = 0;
201
+const RootFatalErrored = 1;
202
+const RootErrored = 2;
203
+const RootSuspended = 3;
204
+const RootSuspendedWithDelay = 4;
205
+const RootCompleted = 5;
206
+
207
+// Describes where we are in the React execution stack
208
+let executionContext: ExecutionContext = NoContext;
209
+// The root we're working on
210
+let workInProgressRoot: FiberRoot | null = null;
211
+// The fiber we're working on
212
+let workInProgress: Fiber | null = null;
213
+// The expiration time we're rendering
214
+let renderExpirationTime: ExpirationTime = NoWork;
215
+// Whether to root completed, errored, suspended, etc.
216
+let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
217
+// A fatal error, if one is thrown
218
+let workInProgressRootFatalError: mixed = null;
219
+// Most recent event time among processed updates during this render.
220
+// This is conceptually a time stamp but expressed in terms of an ExpirationTime
221
+// because we deal mostly with expiration times in the hot path, so this avoids
222
+// the conversion happening in the hot path.
223
+let workInProgressRootLatestProcessedExpirationTime: ExpirationTime = Sync;
224
+let workInProgressRootLatestSuspenseTimeout: ExpirationTime = Sync;
225
+let workInProgressRootCanSuspendUsingConfig: null | SuspenseConfig = null;
226
+// The work left over by components that were visited during this render. Only
227
+// includes unprocessed updates, not work in bailed out children.
228
+let workInProgressRootNextUnprocessedUpdateTime: ExpirationTime = NoWork;
229
+
230
+// If we're pinged while rendering we don't always restart immediately.
231
+// This flag determines if it might be worthwhile to restart if an opportunity
232
+// happens latere.
233
+let workInProgressRootHasPendingPing: boolean = false;
234
+// The most recent time we committed a fallback. This lets us ensure a train
235
+// model where we don't commit new loading states in too quick succession.
236
+let globalMostRecentFallbackTime: number = 0;
237
+const FALLBACK_THROTTLE_MS: number = 500;
238
+
239
+let nextEffect: Fiber | null = null;
240
+let hasUncaughtError = false;
241
+let firstUncaughtError = null;
242
+let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
243
+
244
+let rootDoesHavePassiveEffects: boolean = false;
245
+let rootWithPendingPassiveEffects: FiberRoot | null = null;
246
+let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoPriority;
247
+let pendingPassiveEffectsExpirationTime: ExpirationTime = NoWork;
248
+let pendingPassiveHookEffectsMount: Array<HookEffect | Fiber> = [];
249
+let pendingPassiveHookEffectsUnmount: Array<HookEffect | Fiber> = [];
250
+let pendingPassiveProfilerEffects: Array<Fiber> = [];
251
+
252
+let rootsWithPendingDiscreteUpdates: Map<
253
+ FiberRoot,
254
+ ExpirationTime,
255
+> | null = null;
256
+
257
+// Use these to prevent an infinite loop of nested updates
258
+const NESTED_UPDATE_LIMIT = 50;
259
+let nestedUpdateCount: number = 0;
260
+let rootWithNestedUpdates: FiberRoot | null = null;
261
+
262
+const NESTED_PASSIVE_UPDATE_LIMIT = 50;
263
+let nestedPassiveUpdateCount: number = 0;
264
+
265
+// Marks the need to reschedule pending interactions at these expiration times
266
+// during the commit phase. This enables them to be traced across components
267
+// that spawn new work during render. E.g. hidden boundaries, suspended SSR
268
+// hydration or SuspenseList.
269
+let spawnedWorkDuringRender: null | Array<ExpirationTime> = null;
270
+
271
+// Expiration times are computed by adding to the current time (the start
272
+// time). However, if two updates are scheduled within the same event, we
273
+// should treat their start times as simultaneous, even if the actual clock
274
+// time has advanced between the first and second call.
275
+
276
+// In other words, because expiration times determine how updates are batched,
277
+// we want all updates of like priority that occur within the same event to
278
+// receive the same expiration time. Otherwise we get tearing.
279
+let currentEventTime: ExpirationTime = NoWork;
280
+
281
+// Dev only flag that tracks if passive effects are currently being flushed.
282
+// We warn about state updates for unmounted components differently in this case.
283
+let isFlushingPassiveEffects = false;
284
+
285
+export function getWorkInProgressRoot(): FiberRoot | null {
286
+ return workInProgressRoot;
287
+}
288
+
289
+export function requestCurrentTimeForUpdate() {
290
+ if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
291
+ // We're inside React, so it's fine to read the actual time.
292
+ return msToExpirationTime(now());
293
+ }
294
+ // We're not inside React, so we may be in the middle of a browser event.
295
+ if (currentEventTime !== NoWork) {
296
+ // Use the same start time for all updates until we enter React again.
297
+ return currentEventTime;
298
+ }
299
+ // This is the first update since React yielded. Compute a new start time.
300
+ currentEventTime = msToExpirationTime(now());
301
+ return currentEventTime;
302
+}
303
+
304
+export function getCurrentTime() {
305
+ return msToExpirationTime(now());
306
+}
307
+
308
+export function computeExpirationForFiber(
309
+ currentTime: ExpirationTime,
310
+ fiber: Fiber,
311
+ suspenseConfig: null | SuspenseConfig,
312
+): ExpirationTime {
313
+ const mode = fiber.mode;
314
+ if ((mode & BlockingMode) === NoMode) {
315
+ return Sync;
316
+ }
317
+
318
+ const priorityLevel = getCurrentPriorityLevel();
319
+ if ((mode & ConcurrentMode) === NoMode) {
320
+ return priorityLevel === ImmediatePriority ? Sync : Batched;
321
+ }
322
+
323
+ if ((executionContext & RenderContext) !== NoContext) {
324
+ // Use whatever time we're already rendering
325
+ // TODO: Should there be a way to opt out, like with `runWithPriority`?
326
+ return renderExpirationTime;
327
+ }
328
+
329
+ let expirationTime;
330
+ if (suspenseConfig !== null) {
331
+ // Compute an expiration time based on the Suspense timeout.
332
+ expirationTime = computeSuspenseExpiration(
333
+ currentTime,
334
+ suspenseConfig.timeoutMs | 0 || LOW_PRIORITY_EXPIRATION,
335
+ );
336
+ } else {
337
+ // Compute an expiration time based on the Scheduler priority.
338
+ switch (priorityLevel) {
339
+ case ImmediatePriority:
340
+ expirationTime = Sync;
341
+ break;
342
+ case UserBlockingPriority:
343
+ // TODO: Rename this to computeUserBlockingExpiration
344
+ expirationTime = computeInteractiveExpiration(currentTime);
345
+ break;
346
+ case NormalPriority:
347
+ case LowPriority: // TODO: Handle LowPriority
348
+ // TODO: Rename this to... something better.
349
+ expirationTime = computeAsyncExpiration(currentTime);
350
+ break;
351
+ case IdlePriority:
352
+ expirationTime = Idle;
353
+ break;
354
+ default:
355
+ invariant(false, 'Expected a valid priority level');
356
+ }
357
+ }
358
+
359
+ // If we're in the middle of rendering a tree, do not update at the same
360
+ // expiration time that is already rendering.
361
+ // TODO: We shouldn't have to do this if the update is on a different root.
362
+ // Refactor computeExpirationForFiber + scheduleUpdate so we have access to
363
+ // the root when we check for this condition.
364
+ if (workInProgressRoot !== null && expirationTime === renderExpirationTime) {
365
+ // This is a trick to move this update into a separate batch
366
+ expirationTime -= 1;
367
+ }
368
+
369
+ return expirationTime;
370
+}
371
+
372
+export function scheduleUpdateOnFiber(
373
+ fiber: Fiber,
374
+ expirationTime: ExpirationTime,
375
+) {
376
+ checkForNestedUpdates();
377
+ warnAboutRenderPhaseUpdatesInDEV(fiber);
378
+
379
+ const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
380
+ if (root === null) {
381
+ warnAboutUpdateOnUnmountedFiberInDEV(fiber);
382
+ return;
383
+ }
384
+
385
+ // TODO: computeExpirationForFiber also reads the priority. Pass the
386
+ // priority as an argument to that function and this one.
387
+ const priorityLevel = getCurrentPriorityLevel();
388
+
389
+ if (expirationTime === Sync) {
390
+ if (
391
+ // Check if we're inside unbatchedUpdates
392
+ (executionContext & LegacyUnbatchedContext) !== NoContext &&
393
+ // Check if we're not already rendering
394
+ (executionContext & (RenderContext | CommitContext)) === NoContext
395
+ ) {
396
+ // Register pending interactions on the root to avoid losing traced interaction data.
397
+ schedulePendingInteractions(root, expirationTime);
398
+
399
+ // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
400
+ // root inside of batchedUpdates should be synchronous, but layout updates
401
+ // should be deferred until the end of the batch.
402
+ performSyncWorkOnRoot(root);
403
+ } else {
404
+ ensureRootIsScheduled(root);
405
+ schedulePendingInteractions(root, expirationTime);
406
+ if (executionContext === NoContext) {
407
+ // Flush the synchronous work now, unless we're already working or inside
408
+ // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
409
+ // scheduleCallbackForFiber to preserve the ability to schedule a callback
410
+ // without immediately flushing it. We only do this for user-initiated
411
+ // updates, to preserve historical behavior of legacy mode.
412
+ flushSyncCallbackQueue();
413
+ }
414
+ }
415
+ } else {
416
+ ensureRootIsScheduled(root);
417
+ schedulePendingInteractions(root, expirationTime);
418
+ }
419
+
420
+ if (
421
+ (executionContext & DiscreteEventContext) !== NoContext &&
422
+ // Only updates at user-blocking priority or greater are considered
423
+ // discrete, even inside a discrete event.
424
+ (priorityLevel === UserBlockingPriority ||
425
+ priorityLevel === ImmediatePriority)
426
+ ) {
427
+ // This is the result of a discrete event. Track the lowest priority
428
+ // discrete update per root so we can flush them early, if needed.
429
+ if (rootsWithPendingDiscreteUpdates === null) {
430
+ rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
431
+ } else {
432
+ const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
433
+ if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {
434
+ rootsWithPendingDiscreteUpdates.set(root, expirationTime);
435
+ }
436
+ }
437
+ }
438
+}
439
+
440
+// This is split into a separate function so we can mark a fiber with pending
441
+// work without treating it as a typical update that originates from an event;
442
+// e.g. retrying a Suspense boundary isn't an update, but it does schedule work
443
+// on a fiber.
444
+function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
445
+ // Update the source fiber's expiration time
446
+ if (fiber.expirationTime < expirationTime) {
447
+ fiber.expirationTime = expirationTime;
448
+ }
449
+ let alternate = fiber.alternate;
450
+ if (alternate !== null && alternate.expirationTime < expirationTime) {
451
+ alternate.expirationTime = expirationTime;
452
+ }
453
+ // Walk the parent path to the root and update the child expiration time.
454
+ let node = fiber.return;
455
+ let root = null;
456
+ if (node === null && fiber.tag === HostRoot) {
457
+ root = fiber.stateNode;
458
+ } else {
459
+ while (node !== null) {
460
+ alternate = node.alternate;
461
+ if (node.childExpirationTime < expirationTime) {
462
+ node.childExpirationTime = expirationTime;
463
+ if (
464
+ alternate !== null &&
465
+ alternate.childExpirationTime < expirationTime
466
+ ) {
467
+ alternate.childExpirationTime = expirationTime;
468
+ }
469
+ } else if (
470
+ alternate !== null &&
471
+ alternate.childExpirationTime < expirationTime
472
+ ) {
473
+ alternate.childExpirationTime = expirationTime;
474
+ }
475
+ if (node.return === null && node.tag === HostRoot) {
476
+ root = node.stateNode;
477
+ break;
478
+ }
479
+ node = node.return;
480
+ }
481
+ }
482
+
483
+ if (root !== null) {
484
+ if (workInProgressRoot === root) {
485
+ // Received an update to a tree that's in the middle of rendering. Mark
486
+ // that's unprocessed work on this root.
487
+ markUnprocessedUpdateTime(expirationTime);
488
+
489
+ if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
490
+ // The root already suspended with a delay, which means this render
491
+ // definitely won't finish. Since we have a new update, let's mark it as
492
+ // suspended now, right before marking the incoming update. This has the
493
+ // effect of interrupting the current render and switching to the update.
494
+ // TODO: This happens to work when receiving an update during the render
495
+ // phase, because of the trick inside computeExpirationForFiber to
496
+ // subtract 1 from `renderExpirationTime` to move it into a
497
+ // separate bucket. But we should probably model it with an exception,
498
+ // using the same mechanism we use to force hydration of a subtree.
499
+ // TODO: This does not account for low pri updates that were already
500
+ // scheduled before the root started rendering. Need to track the next
501
+ // pending expiration time (perhaps by backtracking the return path) and
502
+ // then trigger a restart in the `renderDidSuspendDelayIfPossible` path.
503
+ markRootSuspendedAtTime(root, renderExpirationTime);
504
+ }
505
+ }
506
+ // Mark that the root has a pending update.
507
+ markRootUpdatedAtTime(root, expirationTime);
508
+ }
509
+
510
+ return root;
511
+}
512
+
513
+function getNextRootExpirationTimeToWorkOn(root: FiberRoot): ExpirationTime {
514
+ // Determines the next expiration time that the root should render, taking
515
+ // into account levels that may be suspended, or levels that may have
516
+ // received a ping.
517
+
518
+ const lastExpiredTime = root.lastExpiredTime;
519
+ if (lastExpiredTime !== NoWork) {
520
+ return lastExpiredTime;
521
+ }
522
+
523
+ // "Pending" refers to any update that hasn't committed yet, including if it
524
+ // suspended. The "suspended" range is therefore a subset.
525
+ const firstPendingTime = root.firstPendingTime;
526
+ if (!isRootSuspendedAtTime(root, firstPendingTime)) {
527
+ // The highest priority pending time is not suspended. Let's work on that.
528
+ return firstPendingTime;
529
+ }
530
+
531
+ // If the first pending time is suspended, check if there's a lower priority
532
+ // pending level that we know about. Or check if we received a ping. Work
533
+ // on whichever is higher priority.
534
+ const lastPingedTime = root.lastPingedTime;
535
+ const nextKnownPendingLevel = root.nextKnownPendingLevel;
536
+ const nextLevel =
537
+ lastPingedTime > nextKnownPendingLevel
538
+ ? lastPingedTime
539
+ : nextKnownPendingLevel;
540
+ if (nextLevel <= Idle && firstPendingTime !== nextLevel) {
541
+ // Don't work on Idle/Never priority unless everything else is committed.
542
+ return NoWork;
543
+ }
544
+ return nextLevel;
545
+}
546
+
547
+// Use this function to schedule a task for a root. There's only one task per
548
+// root; if a task was already scheduled, we'll check to make sure the
549
+// expiration time of the existing task is the same as the expiration time of
550
+// the next level that the root has work on. This function is called on every
551
+// update, and right before exiting a task.
552
+function ensureRootIsScheduled(root: FiberRoot) {
553
+ const lastExpiredTime = root.lastExpiredTime;
554
+ if (lastExpiredTime !== NoWork) {
555
+ // Special case: Expired work should flush synchronously.
556
+ root.callbackExpirationTime = Sync;
557
+ root.callbackPriority = ImmediatePriority;
558
+ root.callbackNode = scheduleSyncCallback(
559
+ performSyncWorkOnRoot.bind(null, root),
560
+ );
561
+ return;
562
+ }
563
+
564
+ const expirationTime = getNextRootExpirationTimeToWorkOn(root);
565
+ const existingCallbackNode = root.callbackNode;
566
+ if (expirationTime === NoWork) {
567
+ // There's nothing to work on.
568
+ if (existingCallbackNode !== null) {
569
+ root.callbackNode = null;
570
+ root.callbackExpirationTime = NoWork;
571
+ root.callbackPriority = NoPriority;
572
+ }
573
+ return;
574
+ }
575
+
576
+ // TODO: If this is an update, we already read the current time. Pass the
577
+ // time as an argument.
578
+ const currentTime = requestCurrentTimeForUpdate();
579
+ const priorityLevel = inferPriorityFromExpirationTime(
580
+ currentTime,
581
+ expirationTime,
582
+ );
583
+
584
+ // If there's an existing render task, confirm it has the correct priority and
585
+ // expiration time. Otherwise, we'll cancel it and schedule a new one.
586
+ if (existingCallbackNode !== null) {
587
+ const existingCallbackPriority = root.callbackPriority;
588
+ const existingCallbackExpirationTime = root.callbackExpirationTime;
589
+ if (
590
+ // Callback must have the exact same expiration time.
591
+ existingCallbackExpirationTime === expirationTime &&
592
+ // Callback must have greater or equal priority.
593
+ existingCallbackPriority >= priorityLevel
594
+ ) {
595
+ // Existing callback is sufficient.
596
+ return;
597
+ }
598
+ // Need to schedule a new task.
599
+ // TODO: Instead of scheduling a new task, we should be able to change the
600
+ // priority of the existing one.
601
+ cancelCallback(existingCallbackNode);
602
+ }
603
+
604
+ root.callbackExpirationTime = expirationTime;
605
+ root.callbackPriority = priorityLevel;
606
+
607
+ let callbackNode;
608
+ if (expirationTime === Sync) {
609
+ // Sync React callbacks are scheduled on a special internal queue
610
+ callbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
611
+ } else if (disableSchedulerTimeoutBasedOnReactExpirationTime) {
612
+ callbackNode = scheduleCallback(
613
+ priorityLevel,
614
+ performConcurrentWorkOnRoot.bind(null, root),
615
+ );
616
+ } else {
617
+ callbackNode = scheduleCallback(
618
+ priorityLevel,
619
+ performConcurrentWorkOnRoot.bind(null, root),
620
+ // Compute a task timeout based on the expiration time. This also affects
621
+ // ordering because tasks are processed in timeout order.
622
+ {timeout: expirationTimeToMs(expirationTime) - now()},
623
+ );
624
+ }
625
+
626
+ root.callbackNode = callbackNode;
627
+}
628
+
629
+// This is the entry point for every concurrent task, i.e. anything that
630
+// goes through Scheduler.
631
+function performConcurrentWorkOnRoot(root, didTimeout) {
632
+ // Since we know we're in a React event, we can clear the current
633
+ // event time. The next update will compute a new event time.
634
+ currentEventTime = NoWork;
635
+
636
+ // Check if the render expired.
637
+ if (didTimeout) {
638
+ // The render task took too long to complete. Mark the current time as
639
+ // expired to synchronously render all expired work in a single batch.
640
+ const currentTime = requestCurrentTimeForUpdate();
641
+ markRootExpiredAtTime(root, currentTime);
642
+ // This will schedule a synchronous callback.
643
+ ensureRootIsScheduled(root);
644
+ return null;
645
+ }
646
+
647
+ // Determine the next expiration time to work on, using the fields stored
648
+ // on the root.
649
+ let expirationTime = getNextRootExpirationTimeToWorkOn(root);
650
+ if (expirationTime === NoWork) {
651
+ return null;
652
+ }
653
+ const originalCallbackNode = root.callbackNode;
654
+ invariant(
655
+ (executionContext & (RenderContext | CommitContext)) === NoContext,
656
+ 'Should not already be working.',
657
+ );
658
+
659
+ flushPassiveEffects();
660
+
661
+ let exitStatus = renderRootConcurrent(root, expirationTime);
662
+
663
+ if (exitStatus !== RootIncomplete) {
664
+ if (exitStatus === RootErrored) {
665
+ // If something threw an error, try rendering one more time. We'll
666
+ // render synchronously to block concurrent data mutations, and we'll
667
+ // render at Idle (or lower) so that all pending updates are included.
668
+ // If it still fails after the second attempt, we'll give up and commit
669
+ // the resulting tree.
670
+ expirationTime = expirationTime > Idle ? Idle : expirationTime;
671
+ exitStatus = renderRootSync(root, expirationTime);
672
+ }
673
+
674
+ if (exitStatus === RootFatalErrored) {
675
+ const fatalError = workInProgressRootFatalError;
676
+ prepareFreshStack(root, expirationTime);
677
+ markRootSuspendedAtTime(root, expirationTime);
678
+ ensureRootIsScheduled(root);
679
+ throw fatalError;
680
+ }
681
+
682
+ // We now have a consistent tree. The next step is either to commit it,
683
+ // or, if something suspended, wait to commit it after a timeout.
684
+ const finishedWork: Fiber = (root.current.alternate: any);
685
+ root.finishedWork = finishedWork;
686
+ root.finishedExpirationTime = expirationTime;
687
+ root.nextKnownPendingLevel = getRemainingExpirationTime(finishedWork);
688
+ finishConcurrentRender(root, finishedWork, exitStatus, expirationTime);
689
+ }
690
+
691
+ ensureRootIsScheduled(root);
692
+ if (root.callbackNode === originalCallbackNode) {
693
+ // The task node scheduled for this root is the same one that's
694
+ // currently executed. Need to return a continuation.
695
+ return performConcurrentWorkOnRoot.bind(null, root);
696
+ }
697
+ return null;
698
+}
699
+
700
+function finishConcurrentRender(
701
+ root,
702
+ finishedWork,
703
+ exitStatus,
704
+ expirationTime,
705
+) {
706
+ switch (exitStatus) {
707
+ case RootIncomplete:
708
+ case RootFatalErrored: {
709
+ invariant(false, 'Root did not complete. This is a bug in React.');
710
+ }
711
+ // Flow knows about invariant, so it complains if I add a break
712
+ // statement, but eslint doesn't know about invariant, so it complains
713
+ // if I do. eslint-disable-next-line no-fallthrough
714
+ case RootErrored: {
715
+ // We should have already attempted to retry this tree. If we reached
716
+ // this point, it errored again. Commit it.
717
+ commitRoot(root);
718
+ break;
719
+ }
720
+ case RootSuspended: {
721
+ markRootSuspendedAtTime(root, expirationTime);
722
+ const lastSuspendedTime = root.lastSuspendedTime;
723
+
724
+ // We have an acceptable loading state. We need to figure out if we
725
+ // should immediately commit it or wait a bit.
726
+
727
+ // If we have processed new updates during this render, we may now
728
+ // have a new loading state ready. We want to ensure that we commit
729
+ // that as soon as possible.
730
+ const hasNotProcessedNewUpdates =
731
+ workInProgressRootLatestProcessedExpirationTime === Sync;
732
+ if (
733
+ hasNotProcessedNewUpdates &&
734
+ // do not delay if we're inside an act() scope
735
+ !(
736
+ __DEV__ &&
737
+ flushSuspenseFallbacksInTests &&
738
+ IsThisRendererActing.current
739
+ )
740
+ ) {
741
+ // If we have not processed any new updates during this pass, then
742
+ // this is either a retry of an existing fallback state or a
743
+ // hidden tree. Hidden trees shouldn't be batched with other work
744
+ // and after that's fixed it can only be a retry. We're going to
745
+ // throttle committing retries so that we don't show too many
746
+ // loading states too quickly.
747
+ const msUntilTimeout =
748
+ globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now();
749
+ // Don't bother with a very short suspense time.
750
+ if (msUntilTimeout > 10) {
751
+ if (workInProgressRootHasPendingPing) {
752
+ const lastPingedTime = root.lastPingedTime;
753
+ if (lastPingedTime === NoWork || lastPingedTime >= expirationTime) {
754
+ // This render was pinged but we didn't get to restart
755
+ // earlier so try restarting now instead.
756
+ root.lastPingedTime = expirationTime;
757
+ prepareFreshStack(root, expirationTime);
758
+ break;
759
+ }
760
+ }
761
+
762
+ const nextTime = getNextRootExpirationTimeToWorkOn(root);
763
+ if (nextTime !== NoWork && nextTime !== expirationTime) {
764
+ // There's additional work on this root.
765
+ break;
766
+ }
767
+ if (
768
+ lastSuspendedTime !== NoWork &&
769
+ lastSuspendedTime !== expirationTime
770
+ ) {
771
+ // We should prefer to render the fallback of at the last
772
+ // suspended level. Ping the last suspended level to try
773
+ // rendering it again.
774
+ root.lastPingedTime = lastSuspendedTime;
775
+ break;
776
+ }
777
+
778
+ // The render is suspended, it hasn't timed out, and there's no
779
+ // lower priority work to do. Instead of committing the fallback
780
+ // immediately, wait for more data to arrive.
781
+ root.timeoutHandle = scheduleTimeout(
782
+ commitRoot.bind(null, root),
783
+ msUntilTimeout,
784
+ );
785
+ break;
786
+ }
787
+ }
788
+ // The work expired. Commit immediately.
789
+ commitRoot(root);
790
+ break;
791
+ }
792
+ case RootSuspendedWithDelay: {
793
+ markRootSuspendedAtTime(root, expirationTime);
794
+ const lastSuspendedTime = root.lastSuspendedTime;
795
+
796
+ if (
797
+ // do not delay if we're inside an act() scope
798
+ !(
799
+ __DEV__ &&
800
+ flushSuspenseFallbacksInTests &&
801
+ IsThisRendererActing.current
802
+ )
803
+ ) {
804
+ // We're suspended in a state that should be avoided. We'll try to
805
+ // avoid committing it for as long as the timeouts let us.
806
+ if (workInProgressRootHasPendingPing) {
807
+ const lastPingedTime = root.lastPingedTime;
808
+ if (lastPingedTime === NoWork || lastPingedTime >= expirationTime) {
809
+ // This render was pinged but we didn't get to restart earlier
810
+ // so try restarting now instead.
811
+ root.lastPingedTime = expirationTime;
812
+ prepareFreshStack(root, expirationTime);
813
+ break;
814
+ }
815
+ }
816
+
817
+ const nextTime = getNextRootExpirationTimeToWorkOn(root);
818
+ if (nextTime !== NoWork && nextTime !== expirationTime) {
819
+ // There's additional work on this root.
820
+ break;
821
+ }
822
+ if (
823
+ lastSuspendedTime !== NoWork &&
824
+ lastSuspendedTime !== expirationTime
825
+ ) {
826
+ // We should prefer to render the fallback of at the last
827
+ // suspended level. Ping the last suspended level to try
828
+ // rendering it again.
829
+ root.lastPingedTime = lastSuspendedTime;
830
+ break;
831
+ }
832
+
833
+ let msUntilTimeout;
834
+ if (workInProgressRootLatestSuspenseTimeout !== Sync) {
835
+ // We have processed a suspense config whose expiration time we
836
+ // can use as the timeout.
837
+ msUntilTimeout =
838
+ expirationTimeToMs(workInProgressRootLatestSuspenseTimeout) - now();
839
+ } else if (workInProgressRootLatestProcessedExpirationTime === Sync) {
840
+ // This should never normally happen because only new updates
841
+ // cause delayed states, so we should have processed something.
842
+ // However, this could also happen in an offscreen tree.
843
+ msUntilTimeout = 0;
844
+ } else {
845
+ // If we don't have a suspense config, we're going to use a
846
+ // heuristic to determine how long we can suspend.
847
+ const eventTimeMs: number = inferTimeFromExpirationTime(
848
+ workInProgressRootLatestProcessedExpirationTime,
849
+ );
850
+ const currentTimeMs = now();
851
+ const timeUntilExpirationMs =
852
+ expirationTimeToMs(expirationTime) - currentTimeMs;
853
+ let timeElapsed = currentTimeMs - eventTimeMs;
854
+ if (timeElapsed < 0) {
855
+ // We get this wrong some time since we estimate the time.
856
+ timeElapsed = 0;
857
+ }
858
+
859
+ msUntilTimeout = jnd(timeElapsed) - timeElapsed;
860
+
861
+ // Clamp the timeout to the expiration time. TODO: Once the
862
+ // event time is exact instead of inferred from expiration time
863
+ // we don't need this.
864
+ if (timeUntilExpirationMs < msUntilTimeout) {
865
+ msUntilTimeout = timeUntilExpirationMs;
866
+ }
867
+ }
868
+
869
+ // Don't bother with a very short suspense time.
870
+ if (msUntilTimeout > 10) {
871
+ // The render is suspended, it hasn't timed out, and there's no
872
+ // lower priority work to do. Instead of committing the fallback
873
+ // immediately, wait for more data to arrive.
874
+ root.timeoutHandle = scheduleTimeout(
875
+ commitRoot.bind(null, root),
876
+ msUntilTimeout,
877
+ );
878
+ break;
879
+ }
880
+ }
881
+ // The work expired. Commit immediately.
882
+ commitRoot(root);
883
+ break;
884
+ }
885
+ case RootCompleted: {
886
+ // The work completed. Ready to commit.
887
+ if (
888
+ // do not delay if we're inside an act() scope
889
+ !(
890
+ __DEV__ &&
891
+ flushSuspenseFallbacksInTests &&
892
+ IsThisRendererActing.current
893
+ ) &&
894
+ workInProgressRootLatestProcessedExpirationTime !== Sync &&
895
+ workInProgressRootCanSuspendUsingConfig !== null
896
+ ) {
897
+ // If we have exceeded the minimum loading delay, which probably
898
+ // means we have shown a spinner already, we might have to suspend
899
+ // a bit longer to ensure that the spinner is shown for
900
+ // enough time.
901
+ const msUntilTimeout = computeMsUntilSuspenseLoadingDelay(
902
+ workInProgressRootLatestProcessedExpirationTime,
903
+ expirationTime,
904
+ workInProgressRootCanSuspendUsingConfig,
905
+ );
906
+ if (msUntilTimeout > 10) {
907
+ markRootSuspendedAtTime(root, expirationTime);
908
+ root.timeoutHandle = scheduleTimeout(
909
+ commitRoot.bind(null, root),
910
+ msUntilTimeout,
911
+ );
912
+ break;
913
+ }
914
+ }
915
+ commitRoot(root);
916
+ break;
917
+ }
918
+ default: {
919
+ invariant(false, 'Unknown root exit status.');
920
+ }
921
+ }
922
+}
923
+
924
+// This is the entry point for synchronous tasks that don't go
925
+// through Scheduler
926
+function performSyncWorkOnRoot(root) {
927
+ invariant(
928
+ (executionContext & (RenderContext | CommitContext)) === NoContext,
929
+ 'Should not already be working.',
930
+ );
931
+
932
+ flushPassiveEffects();
933
+
934
+ const lastExpiredTime = root.lastExpiredTime;
935
+
936
+ let expirationTime;
937
+ if (lastExpiredTime !== NoWork) {
938
+ // There's expired work on this root. Check if we have a partial tree
939
+ // that we can reuse.
940
+ if (
941
+ root === workInProgressRoot &&
942
+ renderExpirationTime >= lastExpiredTime
943
+ ) {
944
+ // There's a partial tree with equal or greater than priority than the
945
+ // expired level. Finish rendering it before rendering the rest of the
946
+ // expired work.
947
+ expirationTime = renderExpirationTime;
948
+ } else {
949
+ // Start a fresh tree.
950
+ expirationTime = lastExpiredTime;
951
+ }
952
+ } else {
953
+ // There's no expired work. This must be a new, synchronous render.
954
+ expirationTime = Sync;
955
+ }
956
+
957
+ let exitStatus = renderRootSync(root, expirationTime);
958
+
959
+ if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
960
+ // If something threw an error, try rendering one more time. We'll
961
+ // render synchronously to block concurrent data mutations, and we'll
962
+ // render at Idle (or lower) so that all pending updates are included.
963
+ // If it still fails after the second attempt, we'll give up and commit
964
+ // the resulting tree.
965
+ expirationTime = expirationTime > Idle ? Idle : expirationTime;
966
+ exitStatus = renderRootSync(root, expirationTime);
967
+ }
968
+
969
+ if (exitStatus === RootFatalErrored) {
970
+ const fatalError = workInProgressRootFatalError;
971
+ prepareFreshStack(root, expirationTime);
972
+ markRootSuspendedAtTime(root, expirationTime);
973
+ ensureRootIsScheduled(root);
974
+ throw fatalError;
975
+ }
976
+
977
+ // We now have a consistent tree. Because this is a sync render, we
978
+ // will commit it even if something suspended.
979
+ const finishedWork: Fiber = (root.current.alternate: any);
980
+ root.finishedWork = finishedWork;
981
+ root.finishedExpirationTime = expirationTime;
982
+ root.nextKnownPendingLevel = getRemainingExpirationTime(finishedWork);
983
+ commitRoot(root);
984
+
985
+ // Before exiting, make sure there's a callback scheduled for the next
986
+ // pending level.
987
+ ensureRootIsScheduled(root);
988
+
989
+ return null;
990
+}
991
+
992
+export function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
993
+ markRootExpiredAtTime(root, expirationTime);
994
+ ensureRootIsScheduled(root);
995
+ if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
996
+ flushSyncCallbackQueue();
997
+ }
998
+}
999
+
1000
+export function flushDiscreteUpdates() {
1001
+ // TODO: Should be able to flush inside batchedUpdates, but not inside `act`.
1002
+ // However, `act` uses `batchedUpdates`, so there's no way to distinguish
1003
+ // those two cases. Need to fix this before exposing flushDiscreteUpdates
1004
+ // as a public API.
1005
+ if (
1006
+ (executionContext & (BatchedContext | RenderContext | CommitContext)) !==
1007
+ NoContext
1008
+ ) {
1009
+ if (__DEV__) {
1010
+ if ((executionContext & RenderContext) !== NoContext) {
1011
+ console.error(
1012
+ 'unstable_flushDiscreteUpdates: Cannot flush updates when React is ' +
1013
+ 'already rendering.',
1014
+ );
1015
+ }
1016
+ }
1017
+ // We're already rendering, so we can't synchronously flush pending work.
1018
+ // This is probably a nested event dispatch triggered by a lifecycle/effect,
1019
+ // like `el.focus()`. Exit.
1020
+ return;
1021
+ }
1022
+ flushPendingDiscreteUpdates();
1023
+ // If the discrete updates scheduled passive effects, flush them now so that
1024
+ // they fire before the next serial event.
1025
+ flushPassiveEffects();
1026
+}
1027
+
1028
+export function deferredUpdates<A>(fn: () => A): A {
1029
+ // TODO: Remove in favor of Scheduler.next
1030
+ return runWithPriority(NormalPriority, fn);
1031
+}
1032
+
1033
+export function syncUpdates<A, B, C, R>(
1034
+ fn: (A, B, C) => R,
1035
+ a: A,
1036
+ b: B,
1037
+ c: C,
1038
+): R {
1039
+ return runWithPriority(ImmediatePriority, fn.bind(null, a, b, c));
1040
+}
1041
+
1042
+function flushPendingDiscreteUpdates() {
1043
+ if (rootsWithPendingDiscreteUpdates !== null) {
1044
+ // For each root with pending discrete updates, schedule a callback to
1045
+ // immediately flush them.
1046
+ const roots = rootsWithPendingDiscreteUpdates;
1047
+ rootsWithPendingDiscreteUpdates = null;
1048
+ roots.forEach((expirationTime, root) => {
1049
+ markRootExpiredAtTime(root, expirationTime);
1050
+ ensureRootIsScheduled(root);
1051
+ });
1052
+ // Now flush the immediate queue.
1053
+ flushSyncCallbackQueue();
1054
+ }
1055
+}
1056
+
1057
+export function batchedUpdates<A, R>(fn: A => R, a: A): R {
1058
+ const prevExecutionContext = executionContext;
1059
+ executionContext |= BatchedContext;
1060
+ try {
1061
+ return fn(a);
1062
+ } finally {
1063
+ executionContext = prevExecutionContext;
1064
+ if (executionContext === NoContext) {
1065
+ // Flush the immediate callbacks that were scheduled during this batch
1066
+ flushSyncCallbackQueue();
1067
+ }
1068
+ }
1069
+}
1070
+
1071
+export function batchedEventUpdates<A, R>(fn: A => R, a: A): R {
1072
+ const prevExecutionContext = executionContext;
1073
+ executionContext |= EventContext;
1074
+ try {
1075
+ return fn(a);
1076
+ } finally {
1077
+ executionContext = prevExecutionContext;
1078
+ if (executionContext === NoContext) {
1079
+ // Flush the immediate callbacks that were scheduled during this batch
1080
+ flushSyncCallbackQueue();
1081
+ }
1082
+ }
1083
+}
1084
+
1085
+export function discreteUpdates<A, B, C, D, R>(
1086
+ fn: (A, B, C) => R,
1087
+ a: A,
1088
+ b: B,
1089
+ c: C,
1090
+ d: D,
1091
+): R {
1092
+ const prevExecutionContext = executionContext;
1093
+ executionContext |= DiscreteEventContext;
1094
+ try {
1095
+ // Should this
1096
+ return runWithPriority(UserBlockingPriority, fn.bind(null, a, b, c, d));
1097
+ } finally {
1098
+ executionContext = prevExecutionContext;
1099
+ if (executionContext === NoContext) {
1100
+ // Flush the immediate callbacks that were scheduled during this batch
1101
+ flushSyncCallbackQueue();
1102
+ }
1103
+ }
1104
+}
1105
+
1106
+export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1107
+ const prevExecutionContext = executionContext;
1108
+ executionContext &= ~BatchedContext;
1109
+ executionContext |= LegacyUnbatchedContext;
1110
+ try {
1111
+ return fn(a);
1112
+ } finally {
1113
+ executionContext = prevExecutionContext;
1114
+ if (executionContext === NoContext) {
1115
+ // Flush the immediate callbacks that were scheduled during this batch
1116
+ flushSyncCallbackQueue();
1117
+ }
1118
+ }
1119
+}
1120
+
1121
+export function flushSync<A, R>(fn: A => R, a: A): R {
1122
+ if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
1123
+ invariant(
1124
+ false,
1125
+ 'flushSync was called from inside a lifecycle method. It cannot be ' +
1126
+ 'called when React is already rendering.',
1127
+ );
1128
+ }
1129
+ const prevExecutionContext = executionContext;
1130
+ executionContext |= BatchedContext;
1131
+ try {
1132
+ return runWithPriority(ImmediatePriority, fn.bind(null, a));
1133
+ } finally {
1134
+ executionContext = prevExecutionContext;
1135
+ // Flush the immediate callbacks that were scheduled during this batch.
1136
+ // Note that this will happen even if batchedUpdates is higher up
1137
+ // the stack.
1138
+ flushSyncCallbackQueue();
1139
+ }
1140
+}
1141
+
1142
+export function flushControlled(fn: () => mixed): void {
1143
+ const prevExecutionContext = executionContext;
1144
+ executionContext |= BatchedContext;
1145
+ try {
1146
+ runWithPriority(ImmediatePriority, fn);
1147
+ } finally {
1148
+ executionContext = prevExecutionContext;
1149
+ if (executionContext === NoContext) {
1150
+ // Flush the immediate callbacks that were scheduled during this batch
1151
+ flushSyncCallbackQueue();
1152
+ }
1153
+ }
1154
+}
1155
+
1156
+function prepareFreshStack(root, expirationTime) {
1157
+ root.finishedWork = null;
1158
+ root.finishedExpirationTime = NoWork;
1159
+
1160
+ const timeoutHandle = root.timeoutHandle;
1161
+ if (timeoutHandle !== noTimeout) {
1162
+ // The root previous suspended and scheduled a timeout to commit a fallback
1163
+ // state. Now that we have additional work, cancel the timeout.
1164
+ root.timeoutHandle = noTimeout;
1165
+ // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
1166
+ cancelTimeout(timeoutHandle);
1167
+ }
1168
+
1169
+ // Check if there's a suspended level at lower priority.
1170
+ const lastSuspendedTime = root.lastSuspendedTime;
1171
+ if (lastSuspendedTime !== NoWork && lastSuspendedTime < expirationTime) {
1172
+ const lastPingedTime = root.lastPingedTime;
1173
+ // Make sure the suspended level is marked as pinged so that we return back
1174
+ // to it later, in case the render we're about to start gets aborted.
1175
+ // Generally we only reach this path via a ping, but we shouldn't assume
1176
+ // that will always be the case.
1177
+ // Note: This is defensive coding to prevent a pending commit from
1178
+ // being dropped without being rescheduled. It shouldn't be necessary.
1179
+ if (lastPingedTime === NoWork || lastPingedTime > lastSuspendedTime) {
1180
+ root.lastPingedTime = lastSuspendedTime;
1181
+ }
1182
+ }
1183
+
1184
+ if (workInProgress !== null) {
1185
+ let interruptedWork = workInProgress.return;
1186
+ while (interruptedWork !== null) {
1187
+ unwindInterruptedWork(interruptedWork);
1188
+ interruptedWork = interruptedWork.return;
1189
+ }
1190
+ }
1191
+ workInProgressRoot = root;
1192
+ workInProgress = createWorkInProgress(root.current, null);
1193
+ renderExpirationTime = expirationTime;
1194
+ workInProgressRootExitStatus = RootIncomplete;
1195
+ workInProgressRootFatalError = null;
1196
+ workInProgressRootLatestProcessedExpirationTime = Sync;
1197
+ workInProgressRootLatestSuspenseTimeout = Sync;
1198
+ workInProgressRootCanSuspendUsingConfig = null;
1199
+ workInProgressRootNextUnprocessedUpdateTime = NoWork;
1200
+ workInProgressRootHasPendingPing = false;
1201
+
1202
+ if (enableSchedulerTracing) {
1203
+ spawnedWorkDuringRender = null;
1204
+ }
1205
+
1206
+ if (__DEV__) {
1207
+ ReactStrictModeWarnings.discardPendingWarnings();
1208
+ }
1209
+}
1210
+
1211
+function handleError(root, thrownValue) {
1212
+ do {
1213
+ try {
1214
+ // Reset module-level state that was set during the render phase.
1215
+ resetContextDependencies();
1216
+ resetHooksAfterThrow();
1217
+ resetCurrentDebugFiberInDEV();
1218
+ // TODO: I found and added this missing line while investigating a
1219
+ // separate issue. Write a regression test using string refs.
1220
+ ReactCurrentOwner.current = null;
1221
+
1222
+ if (workInProgress === null || workInProgress.return === null) {
1223
+ // Expected to be working on a non-root fiber. This is a fatal error
1224
+ // because there's no ancestor that can handle it; the root is
1225
+ // supposed to capture all errors that weren't caught by an error
1226
+ // boundary.
1227
+ workInProgressRootExitStatus = RootFatalErrored;
1228
+ workInProgressRootFatalError = thrownValue;
1229
+ // Set `workInProgress` to null. This represents advancing to the next
1230
+ // sibling, or the parent if there are no siblings. But since the root
1231
+ // has no siblings nor a parent, we set it to null. Usually this is
1232
+ // handled by `completeUnitOfWork` or `unwindWork`, but since we're
1233
+ // interntionally not calling those, we need set it here.
1234
+ // TODO: Consider calling `unwindWork` to pop the contexts.
1235
+ workInProgress = null;
1236
+ return null;
1237
+ }
1238
+
1239
+ if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
1240
+ // Record the time spent rendering before an error was thrown. This
1241
+ // avoids inaccurate Profiler durations in the case of a
1242
+ // suspended render.
1243
+ stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
1244
+ }
1245
+
1246
+ throwException(
1247
+ root,
1248
+ workInProgress.return,
1249
+ workInProgress,
1250
+ thrownValue,
1251
+ renderExpirationTime,
1252
+ );
1253
+ workInProgress = completeUnitOfWork(workInProgress);
1254
+ } catch (yetAnotherThrownValue) {
1255
+ // Something in the return path also threw.
1256
+ thrownValue = yetAnotherThrownValue;
1257
+ continue;
1258
+ }
1259
+ // Return to the normal work loop.
1260
+ return;
1261
+ } while (true);
1262
+}
1263
+
1264
+function pushDispatcher(root) {
1265
+ const prevDispatcher = ReactCurrentDispatcher.current;
1266
+ ReactCurrentDispatcher.current = ContextOnlyDispatcher;
1267
+ if (prevDispatcher === null) {
1268
+ // The React isomorphic package does not include a default dispatcher.
1269
+ // Instead the first renderer will lazily attach one, in order to give
1270
+ // nicer error messages.
1271
+ return ContextOnlyDispatcher;
1272
+ } else {
1273
+ return prevDispatcher;
1274
+ }
1275
+}
1276
+
1277
+function popDispatcher(prevDispatcher) {
1278
+ ReactCurrentDispatcher.current = prevDispatcher;
1279
+}
1280
+
1281
+function pushInteractions(root) {
1282
+ if (enableSchedulerTracing) {
1283
+ const prevInteractions: Set<Interaction> | null = __interactionsRef.current;
1284
+ __interactionsRef.current = root.memoizedInteractions;
1285
+ return prevInteractions;
1286
+ }
1287
+ return null;
1288
+}
1289
+
1290
+function popInteractions(prevInteractions) {
1291
+ if (enableSchedulerTracing) {
1292
+ __interactionsRef.current = prevInteractions;
1293
+ }
1294
+}
1295
+
1296
+export function markCommitTimeOfFallback() {
1297
+ globalMostRecentFallbackTime = now();
1298
+}
1299
+
1300
+export function markRenderEventTimeAndConfig(
1301
+ expirationTime: ExpirationTime,
1302
+ suspenseConfig: null | SuspenseConfig,
1303
+): void {
1304
+ if (
1305
+ expirationTime < workInProgressRootLatestProcessedExpirationTime &&
1306
+ expirationTime > Idle
1307
+ ) {
1308
+ workInProgressRootLatestProcessedExpirationTime = expirationTime;
1309
+ }
1310
+ if (suspenseConfig !== null) {
1311
+ if (
1312
+ expirationTime < workInProgressRootLatestSuspenseTimeout &&
1313
+ expirationTime > Idle
1314
+ ) {
1315
+ workInProgressRootLatestSuspenseTimeout = expirationTime;
1316
+ // Most of the time we only have one config and getting wrong is not bad.
1317
+ workInProgressRootCanSuspendUsingConfig = suspenseConfig;
1318
+ }
1319
+ }
1320
+}
1321
+
1322
+export function markUnprocessedUpdateTime(
1323
+ expirationTime: ExpirationTime,
1324
+): void {
1325
+ if (expirationTime > workInProgressRootNextUnprocessedUpdateTime) {
1326
+ workInProgressRootNextUnprocessedUpdateTime = expirationTime;
1327
+ }
1328
+}
1329
+
1330
+export function renderDidSuspend(): void {
1331
+ if (workInProgressRootExitStatus === RootIncomplete) {
1332
+ workInProgressRootExitStatus = RootSuspended;
1333
+ }
1334
+}
1335
+
1336
+export function renderDidSuspendDelayIfPossible(): void {
1337
+ if (
1338
+ workInProgressRootExitStatus === RootIncomplete ||
1339
+ workInProgressRootExitStatus === RootSuspended
1340
+ ) {
1341
+ workInProgressRootExitStatus = RootSuspendedWithDelay;
1342
+ }
1343
+
1344
+ // Check if there's a lower priority update somewhere else in the tree.
1345
+ if (
1346
+ workInProgressRootNextUnprocessedUpdateTime !== NoWork &&
1347
+ workInProgressRoot !== null
1348
+ ) {
1349
+ // Mark the current render as suspended, and then mark that there's a
1350
+ // pending update.
1351
+ // TODO: This should immediately interrupt the current render, instead
1352
+ // of waiting until the next time we yield.
1353
+ markRootSuspendedAtTime(workInProgressRoot, renderExpirationTime);
1354
+ markRootUpdatedAtTime(
1355
+ workInProgressRoot,
1356
+ workInProgressRootNextUnprocessedUpdateTime,
1357
+ );
1358
+ }
1359
+}
1360
+
1361
+export function renderDidError() {
1362
+ if (workInProgressRootExitStatus !== RootCompleted) {
1363
+ workInProgressRootExitStatus = RootErrored;
1364
+ }
1365
+}
1366
+
1367
+// Called during render to determine if anything has suspended.
1368
+// Returns false if we're not sure.
1369
+export function renderHasNotSuspendedYet(): boolean {
1370
+ // If something errored or completed, we can't really be sure,
1371
+ // so those are false.
1372
+ return workInProgressRootExitStatus === RootIncomplete;
1373
+}
1374
+
1375
+function inferTimeFromExpirationTime(expirationTime: ExpirationTime): number {
1376
+ // We don't know exactly when the update was scheduled, but we can infer an
1377
+ // approximate start time from the expiration time.
1378
+ const earliestExpirationTimeMs = expirationTimeToMs(expirationTime);
1379
+ return earliestExpirationTimeMs - LOW_PRIORITY_EXPIRATION;
1380
+}
1381
+
1382
+function inferTimeFromExpirationTimeWithSuspenseConfig(
1383
+ expirationTime: ExpirationTime,
1384
+ suspenseConfig: SuspenseConfig,
1385
+): number {
1386
+ // We don't know exactly when the update was scheduled, but we can infer an
1387
+ // approximate start time from the expiration time by subtracting the timeout
1388
+ // that was added to the event time.
1389
+ const earliestExpirationTimeMs = expirationTimeToMs(expirationTime);
1390
+ return (
1391
+ earliestExpirationTimeMs -
1392
+ (suspenseConfig.timeoutMs | 0 || LOW_PRIORITY_EXPIRATION)
1393
+ );
1394
+}
1395
+
1396
+function renderRootSync(root, expirationTime) {
1397
+ const prevExecutionContext = executionContext;
1398
+ executionContext |= RenderContext;
1399
+ const prevDispatcher = pushDispatcher(root);
1400
+
1401
+ // If the root or expiration time have changed, throw out the existing stack
1402
+ // and prepare a fresh one. Otherwise we'll continue where we left off.
1403
+ if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
1404
+ prepareFreshStack(root, expirationTime);
1405
+ startWorkOnPendingInteractions(root, expirationTime);
1406
+ }
1407
+
1408
+ const prevInteractions = pushInteractions(root);
1409
+ do {
1410
+ try {
1411
+ workLoopSync();
1412
+ break;
1413
+ } catch (thrownValue) {
1414
+ handleError(root, thrownValue);
1415
+ }
1416
+ } while (true);
1417
+ resetContextDependencies();
1418
+ if (enableSchedulerTracing) {
1419
+ popInteractions(((prevInteractions: any): Set<Interaction>));
1420
+ }
1421
+
1422
+ executionContext = prevExecutionContext;
1423
+ popDispatcher(prevDispatcher);
1424
+
1425
+ if (workInProgress !== null) {
1426
+ // This is a sync render, so we should have finished the whole tree.
1427
+ invariant(
1428
+ false,
1429
+ 'Cannot commit an incomplete root. This error is likely caused by a ' +
1430
+ 'bug in React. Please file an issue.',
1431
+ );
1432
+ }
1433
+
1434
+ // Set this to null to indicate there's no in-progress render.
1435
+ workInProgressRoot = null;
1436
+
1437
+ return workInProgressRootExitStatus;
1438
+}
1439
+
1440
+// The work loop is an extremely hot path. Tell Closure not to inline it.
1441
+/** @noinline */
1442
+function workLoopSync() {
1443
+ // Already timed out, so perform work without checking if we need to yield.
1444
+ while (workInProgress !== null) {
1445
+ workInProgress = performUnitOfWork(workInProgress);
1446
+ }
1447
+}
1448
+
1449
+function renderRootConcurrent(root, expirationTime) {
1450
+ const prevExecutionContext = executionContext;
1451
+ executionContext |= RenderContext;
1452
+ const prevDispatcher = pushDispatcher(root);
1453
+
1454
+ // If the root or expiration time have changed, throw out the existing stack
1455
+ // and prepare a fresh one. Otherwise we'll continue where we left off.
1456
+ if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
1457
+ prepareFreshStack(root, expirationTime);
1458
+ startWorkOnPendingInteractions(root, expirationTime);
1459
+ }
1460
+
1461
+ const prevInteractions = pushInteractions(root);
1462
+ do {
1463
+ try {
1464
+ workLoopConcurrent();
1465
+ break;
1466
+ } catch (thrownValue) {
1467
+ handleError(root, thrownValue);
1468
+ }
1469
+ } while (true);
1470
+ resetContextDependencies();
1471
+ if (enableSchedulerTracing) {
1472
+ popInteractions(((prevInteractions: any): Set<Interaction>));
1473
+ }
1474
+
1475
+ popDispatcher(prevDispatcher);
1476
+ executionContext = prevExecutionContext;
1477
+
1478
+ // Check if the tree has completed.
1479
+ if (workInProgress !== null) {
1480
+ // Still work remaining.
1481
+ return RootIncomplete;
1482
+ } else {
1483
+ // Completed the tree.
1484
+ // Set this to null to indicate there's no in-progress render.
1485
+ workInProgressRoot = null;
1486
+
1487
+ // Return the final exit status.
1488
+ return workInProgressRootExitStatus;
1489
+ }
1490
+}
1491
+
1492
+/** @noinline */
1493
+function workLoopConcurrent() {
1494
+ // Perform work until Scheduler asks us to yield
1495
+ while (workInProgress !== null && !shouldYield()) {
1496
+ workInProgress = performUnitOfWork(workInProgress);
1497
+ }
1498
+}
1499
+
1500
+function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
1501
+ // The current, flushed, state of this fiber is the alternate. Ideally
1502
+ // nothing should rely on this, but relying on it here means that we don't
1503
+ // need an additional field on the work in progress.
1504
+ const current = unitOfWork.alternate;
1505
+ setCurrentDebugFiberInDEV(unitOfWork);
1506
+
1507
+ let next;
1508
+ if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
1509
+ startProfilerTimer(unitOfWork);
1510
+ next = beginWork(current, unitOfWork, renderExpirationTime);
1511
+ stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
1512
+ } else {
1513
+ next = beginWork(current, unitOfWork, renderExpirationTime);
1514
+ }
1515
+
1516
+ resetCurrentDebugFiberInDEV();
1517
+ unitOfWork.memoizedProps = unitOfWork.pendingProps;
1518
+ if (next === null) {
1519
+ // If this doesn't spawn new work, complete the current work.
1520
+ next = completeUnitOfWork(unitOfWork);
1521
+ }
1522
+
1523
+ ReactCurrentOwner.current = null;
1524
+ return next;
1525
+}
1526
+
1527
+function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
1528
+ // Attempt to complete the current unit of work, then move to the next
1529
+ // sibling. If there are no more siblings, return to the parent fiber.
1530
+ workInProgress = unitOfWork;
1531
+ do {
1532
+ // The current, flushed, state of this fiber is the alternate. Ideally
1533
+ // nothing should rely on this, but relying on it here means that we don't
1534
+ // need an additional field on the work in progress.
1535
+ const current = workInProgress.alternate;
1536
+ const returnFiber = workInProgress.return;
1537
+
1538
+ // Check if the work completed or if something threw.
1539
+ if ((workInProgress.effectTag & Incomplete) === NoEffect) {
1540
+ setCurrentDebugFiberInDEV(workInProgress);
1541
+ let next;
1542
+ if (
1543
+ !enableProfilerTimer ||
1544
+ (workInProgress.mode & ProfileMode) === NoMode
1545
+ ) {
1546
+ next = completeWork(current, workInProgress, renderExpirationTime);
1547
+ } else {
1548
+ startProfilerTimer(workInProgress);
1549
+ next = completeWork(current, workInProgress, renderExpirationTime);
1550
+ // Update render duration assuming we didn't error.
1551
+ stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
1552
+ }
1553
+ resetCurrentDebugFiberInDEV();
1554
+ resetChildExpirationTime(workInProgress);
1555
+
1556
+ if (next !== null) {
1557
+ // Completing this fiber spawned new work. Work on that next.
1558
+ return next;
1559
+ }
1560
+
1561
+ if (
1562
+ returnFiber !== null &&
1563
+ // Do not append effects to parents if a sibling failed to complete
1564
+ (returnFiber.effectTag & Incomplete) === NoEffect
1565
+ ) {
1566
+ // Append all the effects of the subtree and this fiber onto the effect
1567
+ // list of the parent. The completion order of the children affects the
1568
+ // side-effect order.
1569
+ if (returnFiber.firstEffect === null) {
1570
+ returnFiber.firstEffect = workInProgress.firstEffect;
1571
+ }
1572
+ if (workInProgress.lastEffect !== null) {
1573
+ if (returnFiber.lastEffect !== null) {
1574
+ returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
1575
+ }
1576
+ returnFiber.lastEffect = workInProgress.lastEffect;
1577
+ }
1578
+
1579
+ // If this fiber had side-effects, we append it AFTER the children's
1580
+ // side-effects. We can perform certain side-effects earlier if needed,
1581
+ // by doing multiple passes over the effect list. We don't want to
1582
+ // schedule our own side-effect on our own list because if end up
1583
+ // reusing children we'll schedule this effect onto itself since we're
1584
+ // at the end.
1585
+ const effectTag = workInProgress.effectTag;
1586
+
1587
+ // Skip both NoWork and PerformedWork tags when creating the effect
1588
+ // list. PerformedWork effect is read by React DevTools but shouldn't be
1589
+ // committed.
1590
+ if (effectTag > PerformedWork) {
1591
+ if (returnFiber.lastEffect !== null) {
1592
+ returnFiber.lastEffect.nextEffect = workInProgress;
1593
+ } else {
1594
+ returnFiber.firstEffect = workInProgress;
1595
+ }
1596
+ returnFiber.lastEffect = workInProgress;
1597
+ }
1598
+ }
1599
+ } else {
1600
+ // This fiber did not complete because something threw. Pop values off
1601
+ // the stack without entering the complete phase. If this is a boundary,
1602
+ // capture values if possible.
1603
+ const next = unwindWork(workInProgress, renderExpirationTime);
1604
+
1605
+ // Because this fiber did not complete, don't reset its expiration time.
1606
+
1607
+ if (
1608
+ enableProfilerTimer &&
1609
+ (workInProgress.mode & ProfileMode) !== NoMode
1610
+ ) {
1611
+ // Record the render duration for the fiber that errored.
1612
+ stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
1613
+
1614
+ // Include the time spent working on failed children before continuing.
1615
+ let actualDuration = workInProgress.actualDuration;
1616
+ let child = workInProgress.child;
1617
+ while (child !== null) {
1618
+ actualDuration += child.actualDuration;
1619
+ child = child.sibling;
1620
+ }
1621
+ workInProgress.actualDuration = actualDuration;
1622
+ }
1623
+
1624
+ if (next !== null) {
1625
+ // If completing this work spawned new work, do that next. We'll come
1626
+ // back here again.
1627
+ // Since we're restarting, remove anything that is not a host effect
1628
+ // from the effect tag.
1629
+ next.effectTag &= HostEffectMask;
1630
+ return next;
1631
+ }
1632
+
1633
+ if (returnFiber !== null) {
1634
+ // Mark the parent fiber as incomplete and clear its effect list.
1635
+ returnFiber.firstEffect = returnFiber.lastEffect = null;
1636
+ returnFiber.effectTag |= Incomplete;
1637
+ }
1638
+ }
1639
+
1640
+ const siblingFiber = workInProgress.sibling;
1641
+ if (siblingFiber !== null) {
1642
+ // If there is more work to do in this returnFiber, do that next.
1643
+ return siblingFiber;
1644
+ }
1645
+ // Otherwise, return to the parent
1646
+ workInProgress = returnFiber;
1647
+ } while (workInProgress !== null);
1648
+
1649
+ // We've reached the root.
1650
+ if (workInProgressRootExitStatus === RootIncomplete) {
1651
+ workInProgressRootExitStatus = RootCompleted;
1652
+ }
1653
+ return null;
1654
+}
1655
+
1656
+function getRemainingExpirationTime(fiber: Fiber) {
1657
+ const updateExpirationTime = fiber.expirationTime;
1658
+ const childExpirationTime = fiber.childExpirationTime;
1659
+ return updateExpirationTime > childExpirationTime
1660
+ ? updateExpirationTime
1661
+ : childExpirationTime;
1662
+}
1663
+
1664
+function resetChildExpirationTime(completedWork: Fiber) {
1665
+ if (
1666
+ renderExpirationTime !== Never &&
1667
+ completedWork.childExpirationTime === Never
1668
+ ) {
1669
+ // The children of this component are hidden. Don't bubble their
1670
+ // expiration times.
1671
+ return;
1672
+ }
1673
+
1674
+ let newChildExpirationTime = NoWork;
1675
+
1676
+ // Bubble up the earliest expiration time.
1677
+ if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
1678
+ // In profiling mode, resetChildExpirationTime is also used to reset
1679
+ // profiler durations.
1680
+ let actualDuration = completedWork.actualDuration;
1681
+ let treeBaseDuration = completedWork.selfBaseDuration;
1682
+
1683
+ // When a fiber is cloned, its actualDuration is reset to 0. This value will
1684
+ // only be updated if work is done on the fiber (i.e. it doesn't bailout).
1685
+ // When work is done, it should bubble to the parent's actualDuration. If
1686
+ // the fiber has not been cloned though, (meaning no work was done), then
1687
+ // this value will reflect the amount of time spent working on a previous
1688
+ // render. In that case it should not bubble. We determine whether it was
1689
+ // cloned by comparing the child pointer.
1690
+ const shouldBubbleActualDurations =
1691
+ completedWork.alternate === null ||
1692
+ completedWork.child !== completedWork.alternate.child;
1693
+
1694
+ let child = completedWork.child;
1695
+ while (child !== null) {
1696
+ const childUpdateExpirationTime = child.expirationTime;
1697
+ const childChildExpirationTime = child.childExpirationTime;
1698
+ if (childUpdateExpirationTime > newChildExpirationTime) {
1699
+ newChildExpirationTime = childUpdateExpirationTime;
1700
+ }
1701
+ if (childChildExpirationTime > newChildExpirationTime) {
1702
+ newChildExpirationTime = childChildExpirationTime;
1703
+ }
1704
+ if (shouldBubbleActualDurations) {
1705
+ actualDuration += child.actualDuration;
1706
+ }
1707
+ treeBaseDuration += child.treeBaseDuration;
1708
+ child = child.sibling;
1709
+ }
1710
+ completedWork.actualDuration = actualDuration;
1711
+ completedWork.treeBaseDuration = treeBaseDuration;
1712
+ } else {
1713
+ let child = completedWork.child;
1714
+ while (child !== null) {
1715
+ const childUpdateExpirationTime = child.expirationTime;
1716
+ const childChildExpirationTime = child.childExpirationTime;
1717
+ if (childUpdateExpirationTime > newChildExpirationTime) {
1718
+ newChildExpirationTime = childUpdateExpirationTime;
1719
+ }
1720
+ if (childChildExpirationTime > newChildExpirationTime) {
1721
+ newChildExpirationTime = childChildExpirationTime;
1722
+ }
1723
+ child = child.sibling;
1724
+ }
1725
+ }
1726
+
1727
+ completedWork.childExpirationTime = newChildExpirationTime;
1728
+}
1729
+
1730
+function commitRoot(root) {
1731
+ const renderPriorityLevel = getCurrentPriorityLevel();
1732
+ runWithPriority(
1733
+ ImmediatePriority,
1734
+ commitRootImpl.bind(null, root, renderPriorityLevel),
1735
+ );
1736
+ return null;
1737
+}
1738
+
1739
+function commitRootImpl(root, renderPriorityLevel) {
1740
+ do {
1741
+ // `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which
1742
+ // means `flushPassiveEffects` will sometimes result in additional
1743
+ // passive effects. So we need to keep flushing in a loop until there are
1744
+ // no more pending effects.
1745
+ // TODO: Might be better if `flushPassiveEffects` did not automatically
1746
+ // flush synchronous work at the end, to avoid factoring hazards like this.
1747
+ flushPassiveEffects();
1748
+ } while (rootWithPendingPassiveEffects !== null);
1749
+ flushRenderPhaseStrictModeWarningsInDEV();
1750
+
1751
+ invariant(
1752
+ (executionContext & (RenderContext | CommitContext)) === NoContext,
1753
+ 'Should not already be working.',
1754
+ );
1755
+
1756
+ const finishedWork = root.finishedWork;
1757
+ const expirationTime = root.finishedExpirationTime;
1758
+ if (finishedWork === null) {
1759
+ return null;
1760
+ }
1761
+ root.finishedWork = null;
1762
+ root.finishedExpirationTime = NoWork;
1763
+
1764
+ invariant(
1765
+ finishedWork !== root.current,
1766
+ 'Cannot commit the same tree as before. This error is likely caused by ' +
1767
+ 'a bug in React. Please file an issue.',
1768
+ );
1769
+
1770
+ // commitRoot never returns a continuation; it always finishes synchronously.
1771
+ // So we can clear these now to allow a new callback to be scheduled.
1772
+ root.callbackNode = null;
1773
+ root.callbackExpirationTime = NoWork;
1774
+ root.callbackPriority = NoPriority;
1775
+
1776
+ // Update the first and last pending times on this root. The new first
1777
+ // pending time is whatever is left on the root fiber.
1778
+ const remainingExpirationTimeBeforeCommit = getRemainingExpirationTime(
1779
+ finishedWork,
1780
+ );
1781
+ markRootFinishedAtTime(
1782
+ root,
1783
+ expirationTime,
1784
+ remainingExpirationTimeBeforeCommit,
1785
+ );
1786
+
1787
+ // Clear already finished discrete updates in case that a later call of
1788
+ // `flushDiscreteUpdates` starts a useless render pass which may cancels
1789
+ // a scheduled timeout.
1790
+ if (rootsWithPendingDiscreteUpdates !== null) {
1791
+ const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
1792
+ if (
1793
+ lastDiscreteTime !== undefined &&
1794
+ remainingExpirationTimeBeforeCommit < lastDiscreteTime
1795
+ ) {
1796
+ rootsWithPendingDiscreteUpdates.delete(root);
1797
+ }
1798
+ }
1799
+
1800
+ if (root === workInProgressRoot) {
1801
+ // We can reset these now that they are finished.
1802
+ workInProgressRoot = null;
1803
+ workInProgress = null;
1804
+ renderExpirationTime = NoWork;
1805
+ } else {
1806
+ // This indicates that the last root we worked on is not the same one that
1807
+ // we're committing now. This most commonly happens when a suspended root
1808
+ // times out.
1809
+ }
1810
+
1811
+ // Get the list of effects.
1812
+ let firstEffect;
1813
+ if (finishedWork.effectTag > PerformedWork) {
1814
+ // A fiber's effect list consists only of its children, not itself. So if
1815
+ // the root has an effect, we need to add it to the end of the list. The
1816
+ // resulting list is the set that would belong to the root's parent, if it
1817
+ // had one; that is, all the effects in the tree including the root.
1818
+ if (finishedWork.lastEffect !== null) {
1819
+ finishedWork.lastEffect.nextEffect = finishedWork;
1820
+ firstEffect = finishedWork.firstEffect;
1821
+ } else {
1822
+ firstEffect = finishedWork;
1823
+ }
1824
+ } else {
1825
+ // There is no effect on the root.
1826
+ firstEffect = finishedWork.firstEffect;
1827
+ }
1828
+
1829
+ if (firstEffect !== null) {
1830
+ const prevExecutionContext = executionContext;
1831
+ executionContext |= CommitContext;
1832
+ const prevInteractions = pushInteractions(root);
1833
+
1834
+ // Reset this to null before calling lifecycles
1835
+ ReactCurrentOwner.current = null;
1836
+
1837
+ // The commit phase is broken into several sub-phases. We do a separate pass
1838
+ // of the effect list for each phase: all mutation effects come before all
1839
+ // layout effects, and so on.
1840
+
1841
+ // The first phase a "before mutation" phase. We use this phase to read the
1842
+ // state of the host tree right before we mutate it. This is where
1843
+ // getSnapshotBeforeUpdate is called.
1844
+ prepareForCommit(root.containerInfo);
1845
+ nextEffect = firstEffect;
1846
+ do {
1847
+ if (__DEV__) {
1848
+ invokeGuardedCallback(null, commitBeforeMutationEffects, null);
1849
+ if (hasCaughtError()) {
1850
+ invariant(nextEffect !== null, 'Should be working on an effect.');
1851
+ const error = clearCaughtError();
1852
+ captureCommitPhaseError(nextEffect, error);
1853
+ nextEffect = nextEffect.nextEffect;
1854
+ }
1855
+ } else {
1856
+ try {
1857
+ commitBeforeMutationEffects();
1858
+ } catch (error) {
1859
+ invariant(nextEffect !== null, 'Should be working on an effect.');
1860
+ captureCommitPhaseError(nextEffect, error);
1861
+ nextEffect = nextEffect.nextEffect;
1862
+ }
1863
+ }
1864
+ } while (nextEffect !== null);
1865
+
1866
+ if (enableProfilerTimer) {
1867
+ // Mark the current commit time to be shared by all Profilers in this
1868
+ // batch. This enables them to be grouped later.
1869
+ recordCommitTime();
1870
+ }
1871
+
1872
+ // The next phase is the mutation phase, where we mutate the host tree.
1873
+ nextEffect = firstEffect;
1874
+ do {
1875
+ if (__DEV__) {
1876
+ invokeGuardedCallback(
1877
+ null,
1878
+ commitMutationEffects,
1879
+ null,
1880
+ root,
1881
+ renderPriorityLevel,
1882
+ );
1883
+ if (hasCaughtError()) {
1884
+ invariant(nextEffect !== null, 'Should be working on an effect.');
1885
+ const error = clearCaughtError();
1886
+ captureCommitPhaseError(nextEffect, error);
1887
+ nextEffect = nextEffect.nextEffect;
1888
+ }
1889
+ } else {
1890
+ try {
1891
+ commitMutationEffects(root, renderPriorityLevel);
1892
+ } catch (error) {
1893
+ invariant(nextEffect !== null, 'Should be working on an effect.');
1894
+ captureCommitPhaseError(nextEffect, error);
1895
+ nextEffect = nextEffect.nextEffect;
1896
+ }
1897
+ }
1898
+ } while (nextEffect !== null);
1899
+ resetAfterCommit(root.containerInfo);
1900
+
1901
+ // The work-in-progress tree is now the current tree. This must come after
1902
+ // the mutation phase, so that the previous tree is still current during
1903
+ // componentWillUnmount, but before the layout phase, so that the finished
1904
+ // work is current during componentDidMount/Update.
1905
+ root.current = finishedWork;
1906
+
1907
+ // The next phase is the layout phase, where we call effects that read
1908
+ // the host tree after it's been mutated. The idiomatic use case for this is
1909
+ // layout, but class component lifecycles also fire here for legacy reasons.
1910
+ nextEffect = firstEffect;
1911
+ do {
1912
+ if (__DEV__) {
1913
+ invokeGuardedCallback(
1914
+ null,
1915
+ commitLayoutEffects,
1916
+ null,
1917
+ root,
1918
+ expirationTime,
1919
+ );
1920
+ if (hasCaughtError()) {
1921
+ invariant(nextEffect !== null, 'Should be working on an effect.');
1922
+ const error = clearCaughtError();
1923
+ captureCommitPhaseError(nextEffect, error);
1924
+ nextEffect = nextEffect.nextEffect;
1925
+ }
1926
+ } else {
1927
+ try {
1928
+ commitLayoutEffects(root, expirationTime);
1929
+ } catch (error) {
1930
+ invariant(nextEffect !== null, 'Should be working on an effect.');
1931
+ captureCommitPhaseError(nextEffect, error);
1932
+ nextEffect = nextEffect.nextEffect;
1933
+ }
1934
+ }
1935
+ } while (nextEffect !== null);
1936
+
1937
+ nextEffect = null;
1938
+
1939
+ // Tell Scheduler to yield at the end of the frame, so the browser has an
1940
+ // opportunity to paint.
1941
+ requestPaint();
1942
+
1943
+ if (enableSchedulerTracing) {
1944
+ popInteractions(((prevInteractions: any): Set<Interaction>));
1945
+ }
1946
+ executionContext = prevExecutionContext;
1947
+ } else {
1948
+ // No effects.
1949
+ root.current = finishedWork;
1950
+ // Measure these anyway so the flamegraph explicitly shows that there were
1951
+ // no effects.
1952
+ // TODO: Maybe there's a better way to report this.
1953
+ if (enableProfilerTimer) {
1954
+ recordCommitTime();
1955
+ }
1956
+ }
1957
+
1958
+ const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
1959
+
1960
+ if (rootDoesHavePassiveEffects) {
1961
+ // This commit has passive effects. Stash a reference to them. But don't
1962
+ // schedule a callback until after flushing layout work.
1963
+ rootDoesHavePassiveEffects = false;
1964
+ rootWithPendingPassiveEffects = root;
1965
+ pendingPassiveEffectsExpirationTime = expirationTime;
1966
+ pendingPassiveEffectsRenderPriority = renderPriorityLevel;
1967
+ } else {
1968
+ // We are done with the effect chain at this point so let's clear the
1969
+ // nextEffect pointers to assist with GC. If we have passive effects, we'll
1970
+ // clear this in flushPassiveEffects.
1971
+ nextEffect = firstEffect;
1972
+ while (nextEffect !== null) {
1973
+ const nextNextEffect = nextEffect.nextEffect;
1974
+ nextEffect.nextEffect = null;
1975
+ nextEffect = nextNextEffect;
1976
+ }
1977
+ }
1978
+
1979
+ // Check if there's remaining work on this root
1980
+ const remainingExpirationTime = root.firstPendingTime;
1981
+ if (remainingExpirationTime !== NoWork) {
1982
+ if (enableSchedulerTracing) {
1983
+ if (spawnedWorkDuringRender !== null) {
1984
+ const expirationTimes = spawnedWorkDuringRender;
1985
+ spawnedWorkDuringRender = null;
1986
+ for (let i = 0; i < expirationTimes.length; i++) {
1987
+ scheduleInteractions(
1988
+ root,
1989
+ expirationTimes[i],
1990
+ root.memoizedInteractions,
1991
+ );
1992
+ }
1993
+ }
1994
+ schedulePendingInteractions(root, remainingExpirationTime);
1995
+ }
1996
+ } else {
1997
+ // If there's no remaining work, we can clear the set of already failed
1998
+ // error boundaries.
1999
+ legacyErrorBoundariesThatAlreadyFailed = null;
2000
+ }
2001
+
2002
+ if (enableSchedulerTracing) {
2003
+ if (!rootDidHavePassiveEffects) {
2004
+ // If there are no passive effects, then we can complete the pending interactions.
2005
+ // Otherwise, we'll wait until after the passive effects are flushed.
2006
+ // Wait to do this until after remaining work has been scheduled,
2007
+ // so that we don't prematurely signal complete for interactions when there's e.g. hidden work.
2008
+ finishPendingInteractions(root, expirationTime);
2009
+ }
2010
+ }
2011
+
2012
+ if (remainingExpirationTime === Sync) {
2013
+ // Count the number of times the root synchronously re-renders without
2014
+ // finishing. If there are too many, it indicates an infinite update loop.
2015
+ if (root === rootWithNestedUpdates) {
2016
+ nestedUpdateCount++;
2017
+ } else {
2018
+ nestedUpdateCount = 0;
2019
+ rootWithNestedUpdates = root;
2020
+ }
2021
+ } else {
2022
+ nestedUpdateCount = 0;
2023
+ }
2024
+
2025
+ onCommitRoot(finishedWork.stateNode, expirationTime);
2026
+
2027
+ // Always call this before exiting `commitRoot`, to ensure that any
2028
+ // additional work on this root is scheduled.
2029
+ ensureRootIsScheduled(root);
2030
+
2031
+ if (hasUncaughtError) {
2032
+ hasUncaughtError = false;
2033
+ const error = firstUncaughtError;
2034
+ firstUncaughtError = null;
2035
+ throw error;
2036
+ }
2037
+
2038
+ if ((executionContext & LegacyUnbatchedContext) !== NoContext) {
2039
+ // This is a legacy edge case. We just committed the initial mount of
2040
+ // a ReactDOM.render-ed root inside of batchedUpdates. The commit fired
2041
+ // synchronously, but layout updates should be deferred until the end
2042
+ // of the batch.
2043
+ return null;
2044
+ }
2045
+
2046
+ // If layout work was scheduled, flush it now.
2047
+ flushSyncCallbackQueue();
2048
+ return null;
2049
+}
2050
+
2051
+function commitBeforeMutationEffects() {
2052
+ while (nextEffect !== null) {
2053
+ const effectTag = nextEffect.effectTag;
2054
+ if ((effectTag & Snapshot) !== NoEffect) {
2055
+ setCurrentDebugFiberInDEV(nextEffect);
2056
+
2057
+ const current = nextEffect.alternate;
2058
+ commitBeforeMutationEffectOnFiber(current, nextEffect);
2059
+
2060
+ resetCurrentDebugFiberInDEV();
2061
+ }
2062
+ if ((effectTag & Passive) !== NoEffect) {
2063
+ // If there are passive effects, schedule a callback to flush at
2064
+ // the earliest opportunity.
2065
+ if (!rootDoesHavePassiveEffects) {
2066
+ rootDoesHavePassiveEffects = true;
2067
+ scheduleCallback(NormalPriority, () => {
2068
+ flushPassiveEffects();
2069
+ return null;
2070
+ });
2071
+ }
2072
+ }
2073
+ nextEffect = nextEffect.nextEffect;
2074
+ }
2075
+}
2076
+
2077
+function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
2078
+ // TODO: Should probably move the bulk of this function to commitWork.
2079
+ while (nextEffect !== null) {
2080
+ setCurrentDebugFiberInDEV(nextEffect);
2081
+
2082
+ const effectTag = nextEffect.effectTag;
2083
+
2084
+ if (effectTag & ContentReset) {
2085
+ commitResetTextContent(nextEffect);
2086
+ }
2087
+
2088
+ if (effectTag & Ref) {
2089
+ const current = nextEffect.alternate;
2090
+ if (current !== null) {
2091
+ commitDetachRef(current);
2092
+ }
2093
+ }
2094
+
2095
+ // The following switch statement is only concerned about placement,
2096
+ // updates, and deletions. To avoid needing to add a case for every possible
2097
+ // bitmap value, we remove the secondary effects from the effect tag and
2098
+ // switch on that value.
2099
+ const primaryEffectTag =
2100
+ effectTag & (Placement | Update | Deletion | Hydrating);
2101
+ switch (primaryEffectTag) {
2102
+ case Placement: {
2103
+ commitPlacement(nextEffect);
2104
+ // Clear the "placement" from effect tag so that we know that this is
2105
+ // inserted, before any life-cycles like componentDidMount gets called.
2106
+ // TODO: findDOMNode doesn't rely on this any more but isMounted does
2107
+ // and isMounted is deprecated anyway so we should be able to kill this.
2108
+ nextEffect.effectTag &= ~Placement;
2109
+ break;
2110
+ }
2111
+ case PlacementAndUpdate: {
2112
+ // Placement
2113
+ commitPlacement(nextEffect);
2114
+ // Clear the "placement" from effect tag so that we know that this is
2115
+ // inserted, before any life-cycles like componentDidMount gets called.
2116
+ nextEffect.effectTag &= ~Placement;
2117
+
2118
+ // Update
2119
+ const current = nextEffect.alternate;
2120
+ commitWork(current, nextEffect);
2121
+ break;
2122
+ }
2123
+ case Hydrating: {
2124
+ nextEffect.effectTag &= ~Hydrating;
2125
+ break;
2126
+ }
2127
+ case HydratingAndUpdate: {
2128
+ nextEffect.effectTag &= ~Hydrating;
2129
+
2130
+ // Update
2131
+ const current = nextEffect.alternate;
2132
+ commitWork(current, nextEffect);
2133
+ break;
2134
+ }
2135
+ case Update: {
2136
+ const current = nextEffect.alternate;
2137
+ commitWork(current, nextEffect);
2138
+ break;
2139
+ }
2140
+ case Deletion: {
2141
+ commitDeletion(root, nextEffect, renderPriorityLevel);
2142
+ break;
2143
+ }
2144
+ }
2145
+
2146
+ resetCurrentDebugFiberInDEV();
2147
+ nextEffect = nextEffect.nextEffect;
2148
+ }
2149
+}
2150
+
2151
+function commitLayoutEffects(
2152
+ root: FiberRoot,
2153
+ committedExpirationTime: ExpirationTime,
2154
+) {
2155
+ // TODO: Should probably move the bulk of this function to commitWork.
2156
+ while (nextEffect !== null) {
2157
+ setCurrentDebugFiberInDEV(nextEffect);
2158
+
2159
+ const effectTag = nextEffect.effectTag;
2160
+
2161
+ if (effectTag & (Update | Callback)) {
2162
+ const current = nextEffect.alternate;
2163
+ commitLayoutEffectOnFiber(
2164
+ root,
2165
+ current,
2166
+ nextEffect,
2167
+ committedExpirationTime,
2168
+ );
2169
+ }
2170
+
2171
+ if (effectTag & Ref) {
2172
+ commitAttachRef(nextEffect);
2173
+ }
2174
+
2175
+ resetCurrentDebugFiberInDEV();
2176
+ nextEffect = nextEffect.nextEffect;
2177
+ }
2178
+}
2179
+
2180
+export function flushPassiveEffects() {
2181
+ if (pendingPassiveEffectsRenderPriority !== NoPriority) {
2182
+ const priorityLevel =
2183
+ pendingPassiveEffectsRenderPriority > NormalPriority
2184
+ ? NormalPriority
2185
+ : pendingPassiveEffectsRenderPriority;
2186
+ pendingPassiveEffectsRenderPriority = NoPriority;
2187
+ return runWithPriority(priorityLevel, flushPassiveEffectsImpl);
2188
+ }
2189
+}
2190
+
2191
+export function enqueuePendingPassiveProfilerEffect(fiber: Fiber): void {
2192
+ if (enableProfilerTimer && enableProfilerCommitHooks) {
2193
+ pendingPassiveProfilerEffects.push(fiber);
2194
+ if (!rootDoesHavePassiveEffects) {
2195
+ rootDoesHavePassiveEffects = true;
2196
+ scheduleCallback(NormalPriority, () => {
2197
+ flushPassiveEffects();
2198
+ return null;
2199
+ });
2200
+ }
2201
+ }
2202
+}
2203
+
2204
+export function enqueuePendingPassiveHookEffectMount(
2205
+ fiber: Fiber,
2206
+ effect: HookEffect,
2207
+): void {
2208
+ if (runAllPassiveEffectDestroysBeforeCreates) {
2209
+ pendingPassiveHookEffectsMount.push(effect, fiber);
2210
+ if (!rootDoesHavePassiveEffects) {
2211
+ rootDoesHavePassiveEffects = true;
2212
+ scheduleCallback(NormalPriority, () => {
2213
+ flushPassiveEffects();
2214
+ return null;
2215
+ });
2216
+ }
2217
+ }
2218
+}
2219
+
2220
+export function enqueuePendingPassiveHookEffectUnmount(
2221
+ fiber: Fiber,
2222
+ effect: HookEffect,
2223
+): void {
2224
+ if (runAllPassiveEffectDestroysBeforeCreates) {
2225
+ pendingPassiveHookEffectsUnmount.push(effect, fiber);
2226
+ if (!rootDoesHavePassiveEffects) {
2227
+ rootDoesHavePassiveEffects = true;
2228
+ scheduleCallback(NormalPriority, () => {
2229
+ flushPassiveEffects();
2230
+ return null;
2231
+ });
2232
+ }
2233
+ }
2234
+}
2235
+
2236
+function invokePassiveEffectCreate(effect: HookEffect): void {
2237
+ const create = effect.create;
2238
+ effect.destroy = create();
2239
+}
2240
+
2241
+function flushPassiveEffectsImpl() {
2242
+ if (rootWithPendingPassiveEffects === null) {
2243
+ return false;
2244
+ }
2245
+
2246
+ const root = rootWithPendingPassiveEffects;
2247
+ const expirationTime = pendingPassiveEffectsExpirationTime;
2248
+ rootWithPendingPassiveEffects = null;
2249
+ pendingPassiveEffectsExpirationTime = NoWork;
2250
+
2251
+ invariant(
2252
+ (executionContext & (RenderContext | CommitContext)) === NoContext,
2253
+ 'Cannot flush passive effects while already rendering.',
2254
+ );
2255
+
2256
+ if (__DEV__) {
2257
+ isFlushingPassiveEffects = true;
2258
+ }
2259
+
2260
+ const prevExecutionContext = executionContext;
2261
+ executionContext |= CommitContext;
2262
+ const prevInteractions = pushInteractions(root);
2263
+
2264
+ if (runAllPassiveEffectDestroysBeforeCreates) {
2265
+ // It's important that ALL pending passive effect destroy functions are called
2266
+ // before ANY passive effect create functions are called.
2267
+ // Otherwise effects in sibling components might interfere with each other.
2268
+ // e.g. a destroy function in one component may unintentionally override a ref
2269
+ // value set by a create function in another component.
2270
+ // Layout effects have the same constraint.
2271
+
2272
+ // First pass: Destroy stale passive effects.
2273
+ const unmountEffects = pendingPassiveHookEffectsUnmount;
2274
+ pendingPassiveHookEffectsUnmount = [];
2275
+ for (let i = 0; i < unmountEffects.length; i += 2) {
2276
+ const effect = ((unmountEffects[i]: any): HookEffect);
2277
+ const fiber = ((unmountEffects[i + 1]: any): Fiber);
2278
+ const destroy = effect.destroy;
2279
+ effect.destroy = undefined;
2280
+ if (typeof destroy === 'function') {
2281
+ if (__DEV__) {
2282
+ setCurrentDebugFiberInDEV(fiber);
2283
+ if (
2284
+ enableProfilerTimer &&
2285
+ enableProfilerCommitHooks &&
2286
+ fiber.mode & ProfileMode
2287
+ ) {
2288
+ startPassiveEffectTimer();
2289
+ invokeGuardedCallback(null, destroy, null);
2290
+ recordPassiveEffectDuration(fiber);
2291
+ } else {
2292
+ invokeGuardedCallback(null, destroy, null);
2293
+ }
2294
+ if (hasCaughtError()) {
2295
+ invariant(fiber !== null, 'Should be working on an effect.');
2296
+ const error = clearCaughtError();
2297
+ captureCommitPhaseError(fiber, error);
2298
+ }
2299
+ resetCurrentDebugFiberInDEV();
2300
+ } else {
2301
+ try {
2302
+ if (
2303
+ enableProfilerTimer &&
2304
+ enableProfilerCommitHooks &&
2305
+ fiber.mode & ProfileMode
2306
+ ) {
2307
+ try {
2308
+ startPassiveEffectTimer();
2309
+ destroy();
2310
+ } finally {
2311
+ recordPassiveEffectDuration(fiber);
2312
+ }
2313
+ } else {
2314
+ destroy();
2315
+ }
2316
+ } catch (error) {
2317
+ invariant(fiber !== null, 'Should be working on an effect.');
2318
+ captureCommitPhaseError(fiber, error);
2319
+ }
2320
+ }
2321
+ }
2322
+ }
2323
+ // Second pass: Create new passive effects.
2324
+ const mountEffects = pendingPassiveHookEffectsMount;
2325
+ pendingPassiveHookEffectsMount = [];
2326
+ for (let i = 0; i < mountEffects.length; i += 2) {
2327
+ const effect = ((mountEffects[i]: any): HookEffect);
2328
+ const fiber = ((mountEffects[i + 1]: any): Fiber);
2329
+ if (__DEV__) {
2330
+ setCurrentDebugFiberInDEV(fiber);
2331
+ if (
2332
+ enableProfilerTimer &&
2333
+ enableProfilerCommitHooks &&
2334
+ fiber.mode & ProfileMode
2335
+ ) {
2336
+ startPassiveEffectTimer();
2337
+ invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
2338
+ recordPassiveEffectDuration(fiber);
2339
+ } else {
2340
+ invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect);
2341
+ }
2342
+ if (hasCaughtError()) {
2343
+ invariant(fiber !== null, 'Should be working on an effect.');
2344
+ const error = clearCaughtError();
2345
+ captureCommitPhaseError(fiber, error);
2346
+ }
2347
+ resetCurrentDebugFiberInDEV();
2348
+ } else {
2349
+ try {
2350
+ const create = effect.create;
2351
+ if (
2352
+ enableProfilerTimer &&
2353
+ enableProfilerCommitHooks &&
2354
+ fiber.mode & ProfileMode
2355
+ ) {
2356
+ try {
2357
+ startPassiveEffectTimer();
2358
+ effect.destroy = create();
2359
+ } finally {
2360
+ recordPassiveEffectDuration(fiber);
2361
+ }
2362
+ } else {
2363
+ effect.destroy = create();
2364
+ }
2365
+ } catch (error) {
2366
+ invariant(fiber !== null, 'Should be working on an effect.');
2367
+ captureCommitPhaseError(fiber, error);
2368
+ }
2369
+ }
2370
+ }
2371
+ } else {
2372
+ // Note: This currently assumes there are no passive effects on the root fiber
2373
+ // because the root is not part of its own effect list.
2374
+ // This could change in the future.
2375
+ let effect = root.current.firstEffect;
2376
+ while (effect !== null) {
2377
+ if (__DEV__) {
2378
+ setCurrentDebugFiberInDEV(effect);
2379
+ invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
2380
+ if (hasCaughtError()) {
2381
+ invariant(effect !== null, 'Should be working on an effect.');
2382
+ const error = clearCaughtError();
2383
+ captureCommitPhaseError(effect, error);
2384
+ }
2385
+ resetCurrentDebugFiberInDEV();
2386
+ } else {
2387
+ try {
2388
+ commitPassiveHookEffects(effect);
2389
+ } catch (error) {
2390
+ invariant(effect !== null, 'Should be working on an effect.');
2391
+ captureCommitPhaseError(effect, error);
2392
+ }
2393
+ }
2394
+
2395
+ const nextNextEffect = effect.nextEffect;
2396
+ // Remove nextEffect pointer to assist GC
2397
+ effect.nextEffect = null;
2398
+ effect = nextNextEffect;
2399
+ }
2400
+ }
2401
+
2402
+ if (enableProfilerTimer && enableProfilerCommitHooks) {
2403
+ const profilerEffects = pendingPassiveProfilerEffects;
2404
+ pendingPassiveProfilerEffects = [];
2405
+ for (let i = 0; i < profilerEffects.length; i++) {
2406
+ const fiber = ((profilerEffects[i]: any): Fiber);
2407
+ commitPassiveEffectDurations(root, fiber);
2408
+ }
2409
+ }
2410
+
2411
+ if (enableSchedulerTracing) {
2412
+ popInteractions(((prevInteractions: any): Set<Interaction>));
2413
+ finishPendingInteractions(root, expirationTime);
2414
+ }
2415
+
2416
+ if (__DEV__) {
2417
+ isFlushingPassiveEffects = false;
2418
+ }
2419
+
2420
+ executionContext = prevExecutionContext;
2421
+
2422
+ flushSyncCallbackQueue();
2423
+
2424
+ // If additional passive effects were scheduled, increment a counter. If this
2425
+ // exceeds the limit, we'll fire a warning.
2426
+ nestedPassiveUpdateCount =
2427
+ rootWithPendingPassiveEffects === null ? 0 : nestedPassiveUpdateCount + 1;
2428
+
2429
+ return true;
2430
+}
2431
+
2432
+export function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
2433
+ return (
2434
+ legacyErrorBoundariesThatAlreadyFailed !== null &&
2435
+ legacyErrorBoundariesThatAlreadyFailed.has(instance)
2436
+ );
2437
+}
2438
+
2439
+export function markLegacyErrorBoundaryAsFailed(instance: mixed) {
2440
+ if (legacyErrorBoundariesThatAlreadyFailed === null) {
2441
+ legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
2442
+ } else {
2443
+ legacyErrorBoundariesThatAlreadyFailed.add(instance);
2444
+ }
2445
+}
2446
+
2447
+function prepareToThrowUncaughtError(error: mixed) {
2448
+ if (!hasUncaughtError) {
2449
+ hasUncaughtError = true;
2450
+ firstUncaughtError = error;
2451
+ }
2452
+}
2453
+export const onUncaughtError = prepareToThrowUncaughtError;
2454
+
2455
+function captureCommitPhaseErrorOnRoot(
2456
+ rootFiber: Fiber,
2457
+ sourceFiber: Fiber,
2458
+ error: mixed,
2459
+) {
2460
+ const errorInfo = createCapturedValue(error, sourceFiber);
2461
+ const update = createRootErrorUpdate(rootFiber, errorInfo, Sync);
2462
+ enqueueUpdate(rootFiber, update);
2463
+ const root = markUpdateTimeFromFiberToRoot(rootFiber, Sync);
2464
+ if (root !== null) {
2465
+ ensureRootIsScheduled(root);
2466
+ schedulePendingInteractions(root, Sync);
2467
+ }
2468
+}
2469
+
2470
+export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
2471
+ if (sourceFiber.tag === HostRoot) {
2472
+ // Error was thrown at the root. There is no parent, so the root
2473
+ // itself should capture it.
2474
+ captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error);
2475
+ return;
2476
+ }
2477
+
2478
+ let fiber = sourceFiber.return;
2479
+ while (fiber !== null) {
2480
+ if (fiber.tag === HostRoot) {
2481
+ captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error);
2482
+ return;
2483
+ } else if (fiber.tag === ClassComponent) {
2484
+ const ctor = fiber.type;
2485
+ const instance = fiber.stateNode;
2486
+ if (
2487
+ typeof ctor.getDerivedStateFromError === 'function' ||
2488
+ (typeof instance.componentDidCatch === 'function' &&
2489
+ !isAlreadyFailedLegacyErrorBoundary(instance))
2490
+ ) {
2491
+ const errorInfo = createCapturedValue(error, sourceFiber);
2492
+ const update = createClassErrorUpdate(
2493
+ fiber,
2494
+ errorInfo,
2495
+ // TODO: This is always sync
2496
+ Sync,
2497
+ );
2498
+ enqueueUpdate(fiber, update);
2499
+ const root = markUpdateTimeFromFiberToRoot(fiber, Sync);
2500
+ if (root !== null) {
2501
+ ensureRootIsScheduled(root);
2502
+ schedulePendingInteractions(root, Sync);
2503
+ }
2504
+ return;
2505
+ }
2506
+ }
2507
+ fiber = fiber.return;
2508
+ }
2509
+}
2510
+
2511
+export function pingSuspendedRoot(
2512
+ root: FiberRoot,
2513
+ wakeable: Wakeable,
2514
+ suspendedTime: ExpirationTime,
2515
+) {
2516
+ const pingCache = root.pingCache;
2517
+ if (pingCache !== null) {
2518
+ // The wakeable resolved, so we no longer need to memoize, because it will
2519
+ // never be thrown again.
2520
+ pingCache.delete(wakeable);
2521
+ }
2522
+
2523
+ if (workInProgressRoot === root && renderExpirationTime === suspendedTime) {
2524
+ // Received a ping at the same priority level at which we're currently
2525
+ // rendering. We might want to restart this render. This should mirror
2526
+ // the logic of whether or not a root suspends once it completes.
2527
+
2528
+ // TODO: If we're rendering sync either due to Sync, Batched or expired,
2529
+ // we should probably never restart.
2530
+
2531
+ // If we're suspended with delay, we'll always suspend so we can always
2532
+ // restart. If we're suspended without any updates, it might be a retry.
2533
+ // If it's early in the retry we can restart. We can't know for sure
2534
+ // whether we'll eventually process an update during this render pass,
2535
+ // but it's somewhat unlikely that we get to a ping before that, since
2536
+ // getting to the root most update is usually very fast.
2537
+ if (
2538
+ workInProgressRootExitStatus === RootSuspendedWithDelay ||
2539
+ (workInProgressRootExitStatus === RootSuspended &&
2540
+ workInProgressRootLatestProcessedExpirationTime === Sync &&
2541
+ now() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS)
2542
+ ) {
2543
+ // Restart from the root. Don't need to schedule a ping because
2544
+ // we're already working on this tree.
2545
+ prepareFreshStack(root, renderExpirationTime);
2546
+ } else {
2547
+ // Even though we can't restart right now, we might get an
2548
+ // opportunity later. So we mark this render as having a ping.
2549
+ workInProgressRootHasPendingPing = true;
2550
+ }
2551
+ return;
2552
+ }
2553
+
2554
+ if (!isRootSuspendedAtTime(root, suspendedTime)) {
2555
+ // The root is no longer suspended at this time.
2556
+ return;
2557
+ }
2558
+
2559
+ const lastPingedTime = root.lastPingedTime;
2560
+ if (lastPingedTime !== NoWork && lastPingedTime < suspendedTime) {
2561
+ // There's already a lower priority ping scheduled.
2562
+ return;
2563
+ }
2564
+
2565
+ // Mark the time at which this ping was scheduled.
2566
+ root.lastPingedTime = suspendedTime;
2567
+
2568
+ ensureRootIsScheduled(root);
2569
+ schedulePendingInteractions(root, suspendedTime);
2570
+}
2571
+
2572
+function retryTimedOutBoundary(
2573
+ boundaryFiber: Fiber,
2574
+ retryTime: ExpirationTime,
2575
+) {
2576
+ // The boundary fiber (a Suspense component or SuspenseList component)
2577
+ // previously was rendered in its fallback state. One of the promises that
2578
+ // suspended it has resolved, which means at least part of the tree was
2579
+ // likely unblocked. Try rendering again, at a new expiration time.
2580
+ if (retryTime === NoWork) {
2581
+ const suspenseConfig = null; // Retries don't carry over the already committed update.
2582
+ const currentTime = requestCurrentTimeForUpdate();
2583
+ retryTime = computeExpirationForFiber(
2584
+ currentTime,
2585
+ boundaryFiber,
2586
+ suspenseConfig,
2587
+ );
2588
+ }
2589
+ // TODO: Special case idle priority?
2590
+ const root = markUpdateTimeFromFiberToRoot(boundaryFiber, retryTime);
2591
+ if (root !== null) {
2592
+ ensureRootIsScheduled(root);
2593
+ schedulePendingInteractions(root, retryTime);
2594
+ }
2595
+}
2596
+
2597
+export function retryDehydratedSuspenseBoundary(boundaryFiber: Fiber) {
2598
+ const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
2599
+ let retryTime = NoWork;
2600
+ if (suspenseState !== null) {
2601
+ retryTime = suspenseState.retryTime;
2602
+ }
2603
+ retryTimedOutBoundary(boundaryFiber, retryTime);
2604
+}
2605
+
2606
+export function resolveRetryWakeable(boundaryFiber: Fiber, wakeable: Wakeable) {
2607
+ let retryTime = NoWork; // Default
2608
+ let retryCache: WeakSet<Wakeable> | Set<Wakeable> | null;
2609
+ if (enableSuspenseServerRenderer) {
2610
+ switch (boundaryFiber.tag) {
2611
+ case SuspenseComponent:
2612
+ retryCache = boundaryFiber.stateNode;
2613
+ const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
2614
+ if (suspenseState !== null) {
2615
+ retryTime = suspenseState.retryTime;
2616
+ }
2617
+ break;
2618
+ case SuspenseListComponent:
2619
+ retryCache = boundaryFiber.stateNode;
2620
+ break;
2621
+ default:
2622
+ invariant(
2623
+ false,
2624
+ 'Pinged unknown suspense boundary type. ' +
2625
+ 'This is probably a bug in React.',
2626
+ );
2627
+ }
2628
+ } else {
2629
+ retryCache = boundaryFiber.stateNode;
2630
+ }
2631
+
2632
+ if (retryCache !== null) {
2633
+ // The wakeable resolved, so we no longer need to memoize, because it will
2634
+ // never be thrown again.
2635
+ retryCache.delete(wakeable);
2636
+ }
2637
+
2638
+ retryTimedOutBoundary(boundaryFiber, retryTime);
2639
+}
2640
+
2641
+// Computes the next Just Noticeable Difference (JND) boundary.
2642
+// The theory is that a person can't tell the difference between small differences in time.
2643
+// Therefore, if we wait a bit longer than necessary that won't translate to a noticeable
2644
+// difference in the experience. However, waiting for longer might mean that we can avoid
2645
+// showing an intermediate loading state. The longer we have already waited, the harder it
2646
+// is to tell small differences in time. Therefore, the longer we've already waited,
2647
+// the longer we can wait additionally. At some point we have to give up though.
2648
+// We pick a train model where the next boundary commits at a consistent schedule.
2649
+// These particular numbers are vague estimates. We expect to adjust them based on research.
2650
+function jnd(timeElapsed: number) {
2651
+ return timeElapsed < 120
2652
+ ? 120
2653
+ : timeElapsed < 480
2654
+ ? 480
2655
+ : timeElapsed < 1080
2656
+ ? 1080
2657
+ : timeElapsed < 1920
2658
+ ? 1920
2659
+ : timeElapsed < 3000
2660
+ ? 3000
2661
+ : timeElapsed < 4320
2662
+ ? 4320
2663
+ : ceil(timeElapsed / 1960) * 1960;
2664
+}
2665
+
2666
+function computeMsUntilSuspenseLoadingDelay(
2667
+ mostRecentEventTime: ExpirationTime,
2668
+ committedExpirationTime: ExpirationTime,
2669
+ suspenseConfig: SuspenseConfig,
2670
+) {
2671
+ const busyMinDurationMs = (suspenseConfig.busyMinDurationMs: any) | 0;
2672
+ if (busyMinDurationMs <= 0) {
2673
+ return 0;
2674
+ }
2675
+ const busyDelayMs = (suspenseConfig.busyDelayMs: any) | 0;
2676
+
2677
+ // Compute the time until this render pass would expire.
2678
+ const currentTimeMs: number = now();
2679
+ const eventTimeMs: number = inferTimeFromExpirationTimeWithSuspenseConfig(
2680
+ mostRecentEventTime,
2681
+ suspenseConfig,
2682
+ );
2683
+ const timeElapsed = currentTimeMs - eventTimeMs;
2684
+ if (timeElapsed <= busyDelayMs) {
2685
+ // If we haven't yet waited longer than the initial delay, we don't
2686
+ // have to wait any additional time.
2687
+ return 0;
2688
+ }
2689
+ const msUntilTimeout = busyDelayMs + busyMinDurationMs - timeElapsed;
2690
+ // This is the value that is passed to `setTimeout`.
2691
+ return msUntilTimeout;
2692
+}
2693
+
2694
+function checkForNestedUpdates() {
2695
+ if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
2696
+ nestedUpdateCount = 0;
2697
+ rootWithNestedUpdates = null;
2698
+ invariant(
2699
+ false,
2700
+ 'Maximum update depth exceeded. This can happen when a component ' +
2701
+ 'repeatedly calls setState inside componentWillUpdate or ' +
2702
+ 'componentDidUpdate. React limits the number of nested updates to ' +
2703
+ 'prevent infinite loops.',
2704
+ );
2705
+ }
2706
+
2707
+ if (__DEV__) {
2708
+ if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) {
2709
+ nestedPassiveUpdateCount = 0;
2710
+ console.error(
2711
+ 'Maximum update depth exceeded. This can happen when a component ' +
2712
+ "calls setState inside useEffect, but useEffect either doesn't " +
2713
+ 'have a dependency array, or one of the dependencies changes on ' +
2714
+ 'every render.',
2715
+ );
2716
+ }
2717
+ }
2718
+}
2719
+
2720
+function flushRenderPhaseStrictModeWarningsInDEV() {
2721
+ if (__DEV__) {
2722
+ ReactStrictModeWarnings.flushLegacyContextWarning();
2723
+
2724
+ if (warnAboutDeprecatedLifecycles) {
2725
+ ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
2726
+ }
2727
+ }
2728
+}
2729
+
2730
+let didWarnStateUpdateForUnmountedComponent: Set<string> | null = null;
2731
+function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
2732
+ if (__DEV__) {
2733
+ const tag = fiber.tag;
2734
+ if (
2735
+ tag !== HostRoot &&
2736
+ tag !== ClassComponent &&
2737
+ tag !== FunctionComponent &&
2738
+ tag !== ForwardRef &&
2739
+ tag !== MemoComponent &&
2740
+ tag !== SimpleMemoComponent &&
2741
+ tag !== Block
2742
+ ) {
2743
+ // Only warn for user-defined components, not internal ones like Suspense.
2744
+ return;
2745
+ }
2746
+
2747
+ if (
2748
+ deferPassiveEffectCleanupDuringUnmount &&
2749
+ runAllPassiveEffectDestroysBeforeCreates
2750
+ ) {
2751
+ // If there are pending passive effects unmounts for this Fiber,
2752
+ // we can assume that they would have prevented this update.
2753
+ if (pendingPassiveHookEffectsUnmount.indexOf(fiber) >= 0) {
2754
+ return;
2755
+ }
2756
+ }
2757
+
2758
+ // We show the whole stack but dedupe on the top component's name because
2759
+ // the problematic code almost always lies inside that component.
2760
+ const componentName = getComponentName(fiber.type) || 'ReactComponent';
2761
+ if (didWarnStateUpdateForUnmountedComponent !== null) {
2762
+ if (didWarnStateUpdateForUnmountedComponent.has(componentName)) {
2763
+ return;
2764
+ }
2765
+ didWarnStateUpdateForUnmountedComponent.add(componentName);
2766
+ } else {
2767
+ didWarnStateUpdateForUnmountedComponent = new Set([componentName]);
2768
+ }
2769
+
2770
+ if (isFlushingPassiveEffects) {
2771
+ // Do not warn if we are currently flushing passive effects!
2772
+ //
2773
+ // React can't directly detect a memory leak, but there are some clues that warn about one.
2774
+ // One of these clues is when an unmounted React component tries to update its state.
2775
+ // For example, if a component forgets to remove an event listener when unmounting,
2776
+ // that listener may be called later and try to update state,
2777
+ // at which point React would warn about the potential leak.
2778
+ //
2779
+ // Warning signals are the most useful when they're strong.
2780
+ // (So we should avoid false positive warnings.)
2781
+ // Updating state from within an effect cleanup function is sometimes a necessary pattern, e.g.:
2782
+ // 1. Updating an ancestor that a component had registered itself with on mount.
2783
+ // 2. Resetting state when a component is hidden after going offscreen.
2784
+ } else {
2785
+ console.error(
2786
+ "Can't perform a React state update on an unmounted component. This " +
2787
+ 'is a no-op, but it indicates a memory leak in your application. To ' +
2788
+ 'fix, cancel all subscriptions and asynchronous tasks in %s.%s',
2789
+ tag === ClassComponent
2790
+ ? 'the componentWillUnmount method'
2791
+ : 'a useEffect cleanup function',
2792
+ getStackByFiberInDevAndProd(fiber),
2793
+ );
2794
+ }
2795
+ }
2796
+}
2797
+
2798
+let beginWork;
2799
+if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
2800
+ const dummyFiber = null;
2801
+ beginWork = (current, unitOfWork, expirationTime) => {
2802
+ // If a component throws an error, we replay it again in a synchronously
2803
+ // dispatched event, so that the debugger will treat it as an uncaught
2804
+ // error See ReactErrorUtils for more information.
2805
+
2806
+ // Before entering the begin phase, copy the work-in-progress onto a dummy
2807
+ // fiber. If beginWork throws, we'll use this to reset the state.
2808
+ const originalWorkInProgressCopy = assignFiberPropertiesInDEV(
2809
+ dummyFiber,
2810
+ unitOfWork,
2811
+ );
2812
+ try {
2813
+ return originalBeginWork(current, unitOfWork, expirationTime);
2814
+ } catch (originalError) {
2815
+ if (
2816
+ originalError !== null &&
2817
+ typeof originalError === 'object' &&
2818
+ typeof originalError.then === 'function'
2819
+ ) {
2820
+ // Don't replay promises. Treat everything else like an error.
2821
+ throw originalError;
2822
+ }
2823
+
2824
+ // Keep this code in sync with handleError; any changes here must have
2825
+ // corresponding changes there.
2826
+ resetContextDependencies();
2827
+ resetHooksAfterThrow();
2828
+ // Don't reset current debug fiber, since we're about to work on the
2829
+ // same fiber again.
2830
+
2831
+ // Unwind the failed stack frame
2832
+ unwindInterruptedWork(unitOfWork);
2833
+
2834
+ // Restore the original properties of the fiber.
2835
+ assignFiberPropertiesInDEV(unitOfWork, originalWorkInProgressCopy);
2836
+
2837
+ if (enableProfilerTimer && unitOfWork.mode & ProfileMode) {
2838
+ // Reset the profiler timer.
2839
+ startProfilerTimer(unitOfWork);
2840
+ }
2841
+
2842
+ // Run beginWork again.
2843
+ invokeGuardedCallback(
2844
+ null,
2845
+ originalBeginWork,
2846
+ null,
2847
+ current,
2848
+ unitOfWork,
2849
+ expirationTime,
2850
+ );
2851
+
2852
+ if (hasCaughtError()) {
2853
+ const replayError = clearCaughtError();
2854
+ // `invokeGuardedCallback` sometimes sets an expando `_suppressLogging`.
2855
+ // Rethrow this error instead of the original one.
2856
+ throw replayError;
2857
+ } else {
2858
+ // This branch is reachable if the render phase is impure.
2859
+ throw originalError;
2860
+ }
2861
+ }
2862
+ };
2863
+} else {
2864
+ beginWork = originalBeginWork;
2865
+}
2866
+
2867
+let didWarnAboutUpdateInRender = false;
2868
+let didWarnAboutUpdateInRenderForAnotherComponent;
2869
+if (__DEV__) {
2870
+ didWarnAboutUpdateInRenderForAnotherComponent = new Set();
2871
+}
2872
+
2873
+function warnAboutRenderPhaseUpdatesInDEV(fiber) {
2874
+ if (__DEV__) {
2875
+ if (
2876
+ ReactCurrentDebugFiberIsRenderingInDEV &&
2877
+ (executionContext & RenderContext) !== NoContext &&
2878
+ !getIsUpdatingOpaqueValueInRenderPhaseInDEV()
2879
+ ) {
2880
+ switch (fiber.tag) {
2881
+ case FunctionComponent:
2882
+ case ForwardRef:
2883
+ case SimpleMemoComponent: {
2884
+ const renderingComponentName =
2885
+ (workInProgress && getComponentName(workInProgress.type)) ||
2886
+ 'Unknown';
2887
+ // Dedupe by the rendering component because it's the one that needs to be fixed.
2888
+ const dedupeKey = renderingComponentName;
2889
+ if (!didWarnAboutUpdateInRenderForAnotherComponent.has(dedupeKey)) {
2890
+ didWarnAboutUpdateInRenderForAnotherComponent.add(dedupeKey);
2891
+ const setStateComponentName =
2892
+ getComponentName(fiber.type) || 'Unknown';
2893
+ console.error(
2894
+ 'Cannot update a component (`%s`) while rendering a ' +
2895
+ 'different component (`%s`). To locate the bad setState() call inside `%s`, ' +
2896
+ 'follow the stack trace as described in https://fb.me/setstate-in-render',
2897
+ setStateComponentName,
2898
+ renderingComponentName,
2899
+ renderingComponentName,
2900
+ );
2901
+ }
2902
+ break;
2903
+ }
2904
+ case ClassComponent: {
2905
+ if (!didWarnAboutUpdateInRender) {
2906
+ console.error(
2907
+ 'Cannot update during an existing state transition (such as ' +
2908
+ 'within `render`). Render methods should be a pure ' +
2909
+ 'function of props and state.',
2910
+ );
2911
+ didWarnAboutUpdateInRender = true;
2912
+ }
2913
+ break;
2914
+ }
2915
+ }
2916
+ }
2917
+ }
2918
+}
2919
+
2920
+// a 'shared' variable that changes when act() opens/closes in tests.
2921
+export const IsThisRendererActing = {current: (false: boolean)};
2922
+
2923
+export function warnIfNotScopedWithMatchingAct(fiber: Fiber): void {
2924
+ if (__DEV__) {
2925
+ if (
2926
+ warnsIfNotActing === true &&
2927
+ IsSomeRendererActing.current === true &&
2928
+ IsThisRendererActing.current !== true
2929
+ ) {
2930
+ console.error(
2931
+ "It looks like you're using the wrong act() around your test interactions.\n" +
2932
+ 'Be sure to use the matching version of act() corresponding to your renderer:\n\n' +
2933
+ '// for react-dom:\n' +
2934
+ // Break up imports to avoid accidentally parsing them as dependencies.
2935
+ 'import {act} fr' +
2936
+ "om 'react-dom/test-utils';\n" +
2937
+ '// ...\n' +
2938
+ 'act(() => ...);\n\n' +
2939
+ '// for react-test-renderer:\n' +
2940
+ // Break up imports to avoid accidentally parsing them as dependencies.
2941
+ 'import TestRenderer fr' +
2942
+ "om react-test-renderer';\n" +
2943
+ 'const {act} = TestRenderer;\n' +
2944
+ '// ...\n' +
2945
+ 'act(() => ...);' +
2946
+ '%s',
2947
+ getStackByFiberInDevAndProd(fiber),
2948
+ );
2949
+ }
2950
+ }
2951
+}
2952
+
2953
+export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
2954
+ if (__DEV__) {
2955
+ if (
2956
+ warnsIfNotActing === true &&
2957
+ (fiber.mode & StrictMode) !== NoMode &&
2958
+ IsSomeRendererActing.current === false &&
2959
+ IsThisRendererActing.current === false
2960
+ ) {
2961
+ console.error(
2962
+ 'An update to %s ran an effect, but was not wrapped in act(...).\n\n' +
2963
+ 'When testing, code that causes React state updates should be ' +
2964
+ 'wrapped into act(...):\n\n' +
2965
+ 'act(() => {\n' +
2966
+ ' /* fire events that update state */\n' +
2967
+ '});\n' +
2968
+ '/* assert on the output */\n\n' +
2969
+ "This ensures that you're testing the behavior the user would see " +
2970
+ 'in the browser.' +
2971
+ ' Learn more at https://fb.me/react-wrap-tests-with-act' +
2972
+ '%s',
2973
+ getComponentName(fiber.type),
2974
+ getStackByFiberInDevAndProd(fiber),
2975
+ );
2976
+ }
2977
+ }
2978
+}
2979
+
2980
+function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
2981
+ if (__DEV__) {
2982
+ if (
2983
+ warnsIfNotActing === true &&
2984
+ executionContext === NoContext &&
2985
+ IsSomeRendererActing.current === false &&
2986
+ IsThisRendererActing.current === false
2987
+ ) {
2988
+ console.error(
2989
+ 'An update to %s inside a test was not wrapped in act(...).\n\n' +
2990
+ 'When testing, code that causes React state updates should be ' +
2991
+ 'wrapped into act(...):\n\n' +
2992
+ 'act(() => {\n' +
2993
+ ' /* fire events that update state */\n' +
2994
+ '});\n' +
2995
+ '/* assert on the output */\n\n' +
2996
+ "This ensures that you're testing the behavior the user would see " +
2997
+ 'in the browser.' +
2998
+ ' Learn more at https://fb.me/react-wrap-tests-with-act' +
2999
+ '%s',
3000
+ getComponentName(fiber.type),
3001
+ getStackByFiberInDevAndProd(fiber),
3002
+ );
3003
+ }
3004
+ }
3005
+}
3006
+
3007
+export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
3008
+
3009
+// In tests, we want to enforce a mocked scheduler.
3010
+let didWarnAboutUnmockedScheduler = false;
3011
+// TODO Before we release concurrent mode, revisit this and decide whether a mocked
3012
+// scheduler is the actual recommendation. The alternative could be a testing build,
3013
+// a new lib, or whatever; we dunno just yet. This message is for early adopters
3014
+// to get their tests right.
3015
+
3016
+export function warnIfUnmockedScheduler(fiber: Fiber) {
3017
+ if (__DEV__) {
3018
+ if (
3019
+ didWarnAboutUnmockedScheduler === false &&
3020
+ Scheduler.unstable_flushAllWithoutAsserting === undefined
3021
+ ) {
3022
+ if (fiber.mode & BlockingMode || fiber.mode & ConcurrentMode) {
3023
+ didWarnAboutUnmockedScheduler = true;
3024
+ console.error(
3025
+ 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
3026
+ 'to guarantee consistent behaviour across tests and browsers. ' +
3027
+ 'For example, with jest: \n' +
3028
+ // Break up requires to avoid accidentally parsing them as dependencies.
3029
+ "jest.mock('scheduler', () => require" +
3030
+ "('scheduler/unstable_mock'));\n\n" +
3031
+ 'For more info, visit https://fb.me/react-mock-scheduler',
3032
+ );
3033
+ } else if (warnAboutUnmockedScheduler === true) {
3034
+ didWarnAboutUnmockedScheduler = true;
3035
+ console.error(
3036
+ 'Starting from React v17, the "scheduler" module will need to be mocked ' +
3037
+ 'to guarantee consistent behaviour across tests and browsers. ' +
3038
+ 'For example, with jest: \n' +
3039
+ // Break up requires to avoid accidentally parsing them as dependencies.
3040
+ "jest.mock('scheduler', () => require" +
3041
+ "('scheduler/unstable_mock'));\n\n" +
3042
+ 'For more info, visit https://fb.me/react-mock-scheduler',
3043
+ );
3044
+ }
3045
+ }
3046
+ }
3047
+}
3048
+
3049
+function computeThreadID(root, expirationTime) {
3050
+ // Interaction threads are unique per root and expiration time.
3051
+ return expirationTime * 1000 + root.interactionThreadID;
3052
+}
3053
+
3054
+export function markSpawnedWork(expirationTime: ExpirationTime) {
3055
+ if (!enableSchedulerTracing) {
3056
+ return;
3057
+ }
3058
+ if (spawnedWorkDuringRender === null) {
3059
+ spawnedWorkDuringRender = [expirationTime];
3060
+ } else {
3061
+ spawnedWorkDuringRender.push(expirationTime);
3062
+ }
3063
+}
3064
+
3065
+function scheduleInteractions(root, expirationTime, interactions) {
3066
+ if (!enableSchedulerTracing) {
3067
+ return;
3068
+ }
3069
+
3070
+ if (interactions.size > 0) {
3071
+ const pendingInteractionMap = root.pendingInteractionMap;
3072
+ const pendingInteractions = pendingInteractionMap.get(expirationTime);
3073
+ if (pendingInteractions != null) {
3074
+ interactions.forEach(interaction => {
3075
+ if (!pendingInteractions.has(interaction)) {
3076
+ // Update the pending async work count for previously unscheduled interaction.
3077
+ interaction.__count++;
3078
+ }
3079
+
3080
+ pendingInteractions.add(interaction);
3081
+ });
3082
+ } else {
3083
+ pendingInteractionMap.set(expirationTime, new Set(interactions));
3084
+
3085
+ // Update the pending async work count for the current interactions.
3086
+ interactions.forEach(interaction => {
3087
+ interaction.__count++;
3088
+ });
3089
+ }
3090
+
3091
+ const subscriber = __subscriberRef.current;
3092
+ if (subscriber !== null) {
3093
+ const threadID = computeThreadID(root, expirationTime);
3094
+ subscriber.onWorkScheduled(interactions, threadID);
3095
+ }
3096
+ }
3097
+}
3098
+
3099
+function schedulePendingInteractions(root, expirationTime) {
3100
+ // This is called when work is scheduled on a root.
3101
+ // It associates the current interactions with the newly-scheduled expiration.
3102
+ // They will be restored when that expiration is later committed.
3103
+ if (!enableSchedulerTracing) {
3104
+ return;
3105
+ }
3106
+
3107
+ scheduleInteractions(root, expirationTime, __interactionsRef.current);
3108
+}
3109
+
3110
+function startWorkOnPendingInteractions(root, expirationTime) {
3111
+ // This is called when new work is started on a root.
3112
+ if (!enableSchedulerTracing) {
3113
+ return;
3114
+ }
3115
+
3116
+ // Determine which interactions this batch of work currently includes, So that
3117
+ // we can accurately attribute time spent working on it, And so that cascading
3118
+ // work triggered during the render phase will be associated with it.
3119
+ const interactions: Set<Interaction> = new Set();
3120
+ root.pendingInteractionMap.forEach(
3121
+ (scheduledInteractions, scheduledExpirationTime) => {
3122
+ if (scheduledExpirationTime >= expirationTime) {
3123
+ scheduledInteractions.forEach(interaction =>
3124
+ interactions.add(interaction),
3125
+ );
3126
+ }
3127
+ },
3128
+ );
3129
+
3130
+ // Store the current set of interactions on the FiberRoot for a few reasons:
3131
+ // We can re-use it in hot functions like performConcurrentWorkOnRoot()
3132
+ // without having to recalculate it. We will also use it in commitWork() to
3133
+ // pass to any Profiler onRender() hooks. This also provides DevTools with a
3134
+ // way to access it when the onCommitRoot() hook is called.
3135
+ root.memoizedInteractions = interactions;
3136
+
3137
+ if (interactions.size > 0) {
3138
+ const subscriber = __subscriberRef.current;
3139
+ if (subscriber !== null) {
3140
+ const threadID = computeThreadID(root, expirationTime);
3141
+ try {
3142
+ subscriber.onWorkStarted(interactions, threadID);
3143
+ } catch (error) {
3144
+ // If the subscriber throws, rethrow it in a separate task
3145
+ scheduleCallback(ImmediatePriority, () => {
3146
+ throw error;
3147
+ });
3148
+ }
3149
+ }
3150
+ }
3151
+}
3152
+
3153
+function finishPendingInteractions(root, committedExpirationTime) {
3154
+ if (!enableSchedulerTracing) {
3155
+ return;
3156
+ }
3157
+
3158
+ const earliestRemainingTimeAfterCommit = root.firstPendingTime;
3159
+
3160
+ let subscriber;
3161
+
3162
+ try {
3163
+ subscriber = __subscriberRef.current;
3164
+ if (subscriber !== null && root.memoizedInteractions.size > 0) {
3165
+ const threadID = computeThreadID(root, committedExpirationTime);
3166
+ subscriber.onWorkStopped(root.memoizedInteractions, threadID);
3167
+ }
3168
+ } catch (error) {
3169
+ // If the subscriber throws, rethrow it in a separate task
3170
+ scheduleCallback(ImmediatePriority, () => {
3171
+ throw error;
3172
+ });
3173
+ } finally {
3174
+ // Clear completed interactions from the pending Map.
3175
+ // Unless the render was suspended or cascading work was scheduled,
3176
+ // In which case– leave pending interactions until the subsequent render.
3177
+ const pendingInteractionMap = root.pendingInteractionMap;
3178
+ pendingInteractionMap.forEach(
3179
+ (scheduledInteractions, scheduledExpirationTime) => {
3180
+ // Only decrement the pending interaction count if we're done.
3181
+ // If there's still work at the current priority,
3182
+ // That indicates that we are waiting for suspense data.
3183
+ if (scheduledExpirationTime > earliestRemainingTimeAfterCommit) {
3184
+ pendingInteractionMap.delete(scheduledExpirationTime);
3185
+
3186
+ scheduledInteractions.forEach(interaction => {
3187
+ interaction.__count--;
3188
+
3189
+ if (subscriber !== null && interaction.__count === 0) {
3190
+ try {
3191
+ subscriber.onInteractionScheduledWorkCompleted(interaction);
3192
+ } catch (error) {
3193
+ // If the subscriber throws, rethrow it in a separate task
3194
+ scheduleCallback(ImmediatePriority, () => {
3195
+ throw error;
3196
+ });
3197
+ }
3198
+ }
3199
+ });
3200
+ }
3201
+ },
3202
+ );
3203
+ }
3204
+}
packages/react-reconciler/src/ReactMutableSource.new.js
new
+128
@@ -0,0 +1,128 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {ExpirationTime} from './ReactFiberExpirationTime';
11
+import type {FiberRoot} from './ReactInternalTypes';
12
+import type {MutableSource, MutableSourceVersion} from 'shared/ReactTypes';
13
+
14
+import {isPrimaryRenderer} from './ReactFiberHostConfig';
15
+import {NoWork} from './ReactFiberExpirationTime';
16
+
17
+// Work in progress version numbers only apply to a single render,
18
+// and should be reset before starting a new render.
19
+// This tracks which mutable sources need to be reset after a render.
20
+const workInProgressPrimarySources: Array<MutableSource<any>> = [];
21
+const workInProgressSecondarySources: Array<MutableSource<any>> = [];
22
+
23
+let rendererSigil;
24
+if (__DEV__) {
25
+ // Used to detect multiple renderers using the same mutable source.
26
+ rendererSigil = {};
27
+}
28
+
29
+export function clearPendingUpdates(
30
+ root: FiberRoot,
31
+ expirationTime: ExpirationTime,
32
+): void {
33
+ if (expirationTime <= root.mutableSourceLastPendingUpdateTime) {
34
+ // All updates for this source have been processed.
35
+ root.mutableSourceLastPendingUpdateTime = NoWork;
36
+ }
37
+}
38
+
39
+export function getLastPendingExpirationTime(root: FiberRoot): ExpirationTime {
40
+ return root.mutableSourceLastPendingUpdateTime;
41
+}
42
+
43
+export function setPendingExpirationTime(
44
+ root: FiberRoot,
45
+ expirationTime: ExpirationTime,
46
+): void {
47
+ const mutableSourceLastPendingUpdateTime =
48
+ root.mutableSourceLastPendingUpdateTime;
49
+ if (
50
+ mutableSourceLastPendingUpdateTime === NoWork ||
51
+ expirationTime < mutableSourceLastPendingUpdateTime
52
+ ) {
53
+ root.mutableSourceLastPendingUpdateTime = expirationTime;
54
+ }
55
+}
56
+
57
+export function markSourceAsDirty(mutableSource: MutableSource<any>): void {
58
+ if (isPrimaryRenderer) {
59
+ workInProgressPrimarySources.push(mutableSource);
60
+ } else {
61
+ workInProgressSecondarySources.push(mutableSource);
62
+ }
63
+}
64
+
65
+export function resetWorkInProgressVersions(): void {
66
+ if (isPrimaryRenderer) {
67
+ for (let i = 0; i < workInProgressPrimarySources.length; i++) {
68
+ const mutableSource = workInProgressPrimarySources[i];
69
+ mutableSource._workInProgressVersionPrimary = null;
70
+ }
71
+ workInProgressPrimarySources.length = 0;
72
+ } else {
73
+ for (let i = 0; i < workInProgressSecondarySources.length; i++) {
74
+ const mutableSource = workInProgressSecondarySources[i];
75
+ mutableSource._workInProgressVersionSecondary = null;
76
+ }
77
+ workInProgressSecondarySources.length = 0;
78
+ }
79
+}
80
+
81
+export function getWorkInProgressVersion(
82
+ mutableSource: MutableSource<any>,
83
+): null | MutableSourceVersion {
84
+ if (isPrimaryRenderer) {
85
+ return mutableSource._workInProgressVersionPrimary;
86
+ } else {
87
+ return mutableSource._workInProgressVersionSecondary;
88
+ }
89
+}
90
+
91
+export function setWorkInProgressVersion(
92
+ mutableSource: MutableSource<any>,
93
+ version: MutableSourceVersion,
94
+): void {
95
+ if (isPrimaryRenderer) {
96
+ mutableSource._workInProgressVersionPrimary = version;
97
+ workInProgressPrimarySources.push(mutableSource);
98
+ } else {
99
+ mutableSource._workInProgressVersionSecondary = version;
100
+ workInProgressSecondarySources.push(mutableSource);
101
+ }
102
+}
103
+
104
+export function warnAboutMultipleRenderersDEV(
105
+ mutableSource: MutableSource<any>,
106
+): void {
107
+ if (__DEV__) {
108
+ if (isPrimaryRenderer) {
109
+ if (mutableSource._currentPrimaryRenderer == null) {
110
+ mutableSource._currentPrimaryRenderer = rendererSigil;
111
+ } else if (mutableSource._currentPrimaryRenderer !== rendererSigil) {
112
+ console.error(
113
+ 'Detected multiple renderers concurrently rendering the ' +
114
+ 'same mutable source. This is currently unsupported.',
115
+ );
116
+ }
117
+ } else {
118
+ if (mutableSource._currentSecondaryRenderer == null) {
119
+ mutableSource._currentSecondaryRenderer = rendererSigil;
120
+ } else if (mutableSource._currentSecondaryRenderer !== rendererSigil) {
121
+ console.error(
122
+ 'Detected multiple renderers concurrently rendering the ' +
123
+ 'same mutable source. This is currently unsupported.',
124
+ );
125
+ }
126
+ }
127
+ }
128
+}
packages/react-reconciler/src/ReactProfilerTimer.new.js
new
+161
@@ -0,0 +1,161 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactInternalTypes';
11
+
12
+import {
13
+ enableProfilerTimer,
14
+ enableProfilerCommitHooks,
15
+} from 'shared/ReactFeatureFlags';
16
+import {Profiler} from './ReactWorkTags';
17
+
18
+// Intentionally not named imports because Rollup would use dynamic dispatch for
19
+// CommonJS interop named imports.
20
+import * as Scheduler from 'scheduler';
21
+
22
+const {unstable_now: now} = Scheduler;
23
+
24
+export type ProfilerTimer = {
25
+ getCommitTime(): number,
26
+ recordCommitTime(): void,
27
+ startProfilerTimer(fiber: Fiber): void,
28
+ stopProfilerTimerIfRunning(fiber: Fiber): void,
29
+ stopProfilerTimerIfRunningAndRecordDelta(fiber: Fiber): void,
30
+ ...
31
+};
32
+
33
+let commitTime: number = 0;
34
+let layoutEffectStartTime: number = -1;
35
+let profilerStartTime: number = -1;
36
+let passiveEffectStartTime: number = -1;
37
+
38
+function getCommitTime(): number {
39
+ return commitTime;
40
+}
41
+
42
+function recordCommitTime(): void {
43
+ if (!enableProfilerTimer) {
44
+ return;
45
+ }
46
+ commitTime = now();
47
+}
48
+
49
+function startProfilerTimer(fiber: Fiber): void {
50
+ if (!enableProfilerTimer) {
51
+ return;
52
+ }
53
+
54
+ profilerStartTime = now();
55
+
56
+ if (((fiber.actualStartTime: any): number) < 0) {
57
+ fiber.actualStartTime = now();
58
+ }
59
+}
60
+
61
+function stopProfilerTimerIfRunning(fiber: Fiber): void {
62
+ if (!enableProfilerTimer) {
63
+ return;
64
+ }
65
+ profilerStartTime = -1;
66
+}
67
+
68
+function stopProfilerTimerIfRunningAndRecordDelta(
69
+ fiber: Fiber,
70
+ overrideBaseTime: boolean,
71
+): void {
72
+ if (!enableProfilerTimer) {
73
+ return;
74
+ }
75
+
76
+ if (profilerStartTime >= 0) {
77
+ const elapsedTime = now() - profilerStartTime;
78
+ fiber.actualDuration += elapsedTime;
79
+ if (overrideBaseTime) {
80
+ fiber.selfBaseDuration = elapsedTime;
81
+ }
82
+ profilerStartTime = -1;
83
+ }
84
+}
85
+
86
+function recordLayoutEffectDuration(fiber: Fiber): void {
87
+ if (!enableProfilerTimer || !enableProfilerCommitHooks) {
88
+ return;
89
+ }
90
+
91
+ if (layoutEffectStartTime >= 0) {
92
+ const elapsedTime = now() - layoutEffectStartTime;
93
+
94
+ layoutEffectStartTime = -1;
95
+
96
+ // Store duration on the next nearest Profiler ancestor.
97
+ let parentFiber = fiber.return;
98
+ while (parentFiber !== null) {
99
+ if (parentFiber.tag === Profiler) {
100
+ const parentStateNode = parentFiber.stateNode;
101
+ parentStateNode.effectDuration += elapsedTime;
102
+ break;
103
+ }
104
+ parentFiber = parentFiber.return;
105
+ }
106
+ }
107
+}
108
+
109
+function recordPassiveEffectDuration(fiber: Fiber): void {
110
+ if (!enableProfilerTimer || !enableProfilerCommitHooks) {
111
+ return;
112
+ }
113
+
114
+ if (passiveEffectStartTime >= 0) {
115
+ const elapsedTime = now() - passiveEffectStartTime;
116
+
117
+ passiveEffectStartTime = -1;
118
+
119
+ // Store duration on the next nearest Profiler ancestor.
120
+ let parentFiber = fiber.return;
121
+ while (parentFiber !== null) {
122
+ if (parentFiber.tag === Profiler) {
123
+ const parentStateNode = parentFiber.stateNode;
124
+ if (parentStateNode !== null) {
125
+ // Detached fibers have their state node cleared out.
126
+ // In this case, the return pointer is also cleared out,
127
+ // so we won't be able to report the time spent in this Profiler's subtree.
128
+ parentStateNode.passiveEffectDuration += elapsedTime;
129
+ }
130
+ break;
131
+ }
132
+ parentFiber = parentFiber.return;
133
+ }
134
+ }
135
+}
136
+
137
+function startLayoutEffectTimer(): void {
138
+ if (!enableProfilerTimer || !enableProfilerCommitHooks) {
139
+ return;
140
+ }
141
+ layoutEffectStartTime = now();
142
+}
143
+
144
+function startPassiveEffectTimer(): void {
145
+ if (!enableProfilerTimer || !enableProfilerCommitHooks) {
146
+ return;
147
+ }
148
+ passiveEffectStartTime = now();
149
+}
150
+
151
+export {
152
+ getCommitTime,
153
+ recordCommitTime,
154
+ recordLayoutEffectDuration,
155
+ recordPassiveEffectDuration,
156
+ startLayoutEffectTimer,
157
+ startPassiveEffectTimer,
158
+ startProfilerTimer,
159
+ stopProfilerTimerIfRunning,
160
+ stopProfilerTimerIfRunningAndRecordDelta,
161
+};
packages/react-reconciler/src/ReactStrictModeWarnings.new.js
new
+366
@@ -0,0 +1,366 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactInternalTypes';
11
+
12
+import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
13
+
14
+import getComponentName from 'shared/getComponentName';
15
+import {StrictMode} from './ReactTypeOfMode';
16
+
17
+type FiberArray = Array<Fiber>;
18
+type FiberToFiberComponentsMap = Map<Fiber, FiberArray>;
19
+
20
+const ReactStrictModeWarnings = {
21
+ recordUnsafeLifecycleWarnings(fiber: Fiber, instance: any): void {},
22
+ flushPendingUnsafeLifecycleWarnings(): void {},
23
+ recordLegacyContextWarning(fiber: Fiber, instance: any): void {},
24
+ flushLegacyContextWarning(): void {},
25
+ discardPendingWarnings(): void {},
26
+};
27
+
28
+if (__DEV__) {
29
+ const findStrictRoot = (fiber: Fiber): Fiber | null => {
30
+ let maybeStrictRoot = null;
31
+
32
+ let node = fiber;
33
+ while (node !== null) {
34
+ if (node.mode & StrictMode) {
35
+ maybeStrictRoot = node;
36
+ }
37
+ node = node.return;
38
+ }
39
+
40
+ return maybeStrictRoot;
41
+ };
42
+
43
+ const setToSortedString = set => {
44
+ const array = [];
45
+ set.forEach(value => {
46
+ array.push(value);
47
+ });
48
+ return array.sort().join(', ');
49
+ };
50
+
51
+ let pendingComponentWillMountWarnings: Array<Fiber> = [];
52
+ let pendingUNSAFE_ComponentWillMountWarnings: Array<Fiber> = [];
53
+ let pendingComponentWillReceivePropsWarnings: Array<Fiber> = [];
54
+ let pendingUNSAFE_ComponentWillReceivePropsWarnings: Array<Fiber> = [];
55
+ let pendingComponentWillUpdateWarnings: Array<Fiber> = [];
56
+ let pendingUNSAFE_ComponentWillUpdateWarnings: Array<Fiber> = [];
57
+
58
+ // Tracks components we have already warned about.
59
+ const didWarnAboutUnsafeLifecycles = new Set();
60
+
61
+ ReactStrictModeWarnings.recordUnsafeLifecycleWarnings = (
62
+ fiber: Fiber,
63
+ instance: any,
64
+ ) => {
65
+ // Dedup strategy: Warn once per component.
66
+ if (didWarnAboutUnsafeLifecycles.has(fiber.type)) {
67
+ return;
68
+ }
69
+
70
+ if (
71
+ typeof instance.componentWillMount === 'function' &&
72
+ // Don't warn about react-lifecycles-compat polyfilled components.
73
+ instance.componentWillMount.__suppressDeprecationWarning !== true
74
+ ) {
75
+ pendingComponentWillMountWarnings.push(fiber);
76
+ }
77
+
78
+ if (
79
+ fiber.mode & StrictMode &&
80
+ typeof instance.UNSAFE_componentWillMount === 'function'
81
+ ) {
82
+ pendingUNSAFE_ComponentWillMountWarnings.push(fiber);
83
+ }
84
+
85
+ if (
86
+ typeof instance.componentWillReceiveProps === 'function' &&
87
+ instance.componentWillReceiveProps.__suppressDeprecationWarning !== true
88
+ ) {
89
+ pendingComponentWillReceivePropsWarnings.push(fiber);
90
+ }
91
+
92
+ if (
93
+ fiber.mode & StrictMode &&
94
+ typeof instance.UNSAFE_componentWillReceiveProps === 'function'
95
+ ) {
96
+ pendingUNSAFE_ComponentWillReceivePropsWarnings.push(fiber);
97
+ }
98
+
99
+ if (
100
+ typeof instance.componentWillUpdate === 'function' &&
101
+ instance.componentWillUpdate.__suppressDeprecationWarning !== true
102
+ ) {
103
+ pendingComponentWillUpdateWarnings.push(fiber);
104
+ }
105
+
106
+ if (
107
+ fiber.mode & StrictMode &&
108
+ typeof instance.UNSAFE_componentWillUpdate === 'function'
109
+ ) {
110
+ pendingUNSAFE_ComponentWillUpdateWarnings.push(fiber);
111
+ }
112
+ };
113
+
114
+ ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings = () => {
115
+ // We do an initial pass to gather component names
116
+ const componentWillMountUniqueNames = new Set();
117
+ if (pendingComponentWillMountWarnings.length > 0) {
118
+ pendingComponentWillMountWarnings.forEach(fiber => {
119
+ componentWillMountUniqueNames.add(
120
+ getComponentName(fiber.type) || 'Component',
121
+ );
122
+ didWarnAboutUnsafeLifecycles.add(fiber.type);
123
+ });
124
+ pendingComponentWillMountWarnings = [];
125
+ }
126
+
127
+ const UNSAFE_componentWillMountUniqueNames = new Set();
128
+ if (pendingUNSAFE_ComponentWillMountWarnings.length > 0) {
129
+ pendingUNSAFE_ComponentWillMountWarnings.forEach(fiber => {
130
+ UNSAFE_componentWillMountUniqueNames.add(
131
+ getComponentName(fiber.type) || 'Component',
132
+ );
133
+ didWarnAboutUnsafeLifecycles.add(fiber.type);
134
+ });
135
+ pendingUNSAFE_ComponentWillMountWarnings = [];
136
+ }
137
+
138
+ const componentWillReceivePropsUniqueNames = new Set();
139
+ if (pendingComponentWillReceivePropsWarnings.length > 0) {
140
+ pendingComponentWillReceivePropsWarnings.forEach(fiber => {
141
+ componentWillReceivePropsUniqueNames.add(
142
+ getComponentName(fiber.type) || 'Component',
143
+ );
144
+ didWarnAboutUnsafeLifecycles.add(fiber.type);
145
+ });
146
+
147
+ pendingComponentWillReceivePropsWarnings = [];
148
+ }
149
+
150
+ const UNSAFE_componentWillReceivePropsUniqueNames = new Set();
151
+ if (pendingUNSAFE_ComponentWillReceivePropsWarnings.length > 0) {
152
+ pendingUNSAFE_ComponentWillReceivePropsWarnings.forEach(fiber => {
153
+ UNSAFE_componentWillReceivePropsUniqueNames.add(
154
+ getComponentName(fiber.type) || 'Component',
155
+ );
156
+ didWarnAboutUnsafeLifecycles.add(fiber.type);
157
+ });
158
+
159
+ pendingUNSAFE_ComponentWillReceivePropsWarnings = [];
160
+ }
161
+
162
+ const componentWillUpdateUniqueNames = new Set();
163
+ if (pendingComponentWillUpdateWarnings.length > 0) {
164
+ pendingComponentWillUpdateWarnings.forEach(fiber => {
165
+ componentWillUpdateUniqueNames.add(
166
+ getComponentName(fiber.type) || 'Component',
167
+ );
168
+ didWarnAboutUnsafeLifecycles.add(fiber.type);
169
+ });
170
+
171
+ pendingComponentWillUpdateWarnings = [];
172
+ }
173
+
174
+ const UNSAFE_componentWillUpdateUniqueNames = new Set();
175
+ if (pendingUNSAFE_ComponentWillUpdateWarnings.length > 0) {
176
+ pendingUNSAFE_ComponentWillUpdateWarnings.forEach(fiber => {
177
+ UNSAFE_componentWillUpdateUniqueNames.add(
178
+ getComponentName(fiber.type) || 'Component',
179
+ );
180
+ didWarnAboutUnsafeLifecycles.add(fiber.type);
181
+ });
182
+
183
+ pendingUNSAFE_ComponentWillUpdateWarnings = [];
184
+ }
185
+
186
+ // Finally, we flush all the warnings
187
+ // UNSAFE_ ones before the deprecated ones, since they'll be 'louder'
188
+ if (UNSAFE_componentWillMountUniqueNames.size > 0) {
189
+ const sortedNames = setToSortedString(
190
+ UNSAFE_componentWillMountUniqueNames,
191
+ );
192
+ console.error(
193
+ 'Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. ' +
194
+ 'See https://fb.me/react-unsafe-component-lifecycles for details.\n\n' +
195
+ '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
196
+ '\nPlease update the following components: %s',
197
+ sortedNames,
198
+ );
199
+ }
200
+
201
+ if (UNSAFE_componentWillReceivePropsUniqueNames.size > 0) {
202
+ const sortedNames = setToSortedString(
203
+ UNSAFE_componentWillReceivePropsUniqueNames,
204
+ );
205
+ console.error(
206
+ 'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended ' +
207
+ 'and may indicate bugs in your code. ' +
208
+ 'See https://fb.me/react-unsafe-component-lifecycles for details.\n\n' +
209
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
210
+ "* If you're updating state whenever props change, " +
211
+ 'refactor your code to use memoization techniques or move it to ' +
212
+ 'static getDerivedStateFromProps. Learn more at: https://fb.me/react-derived-state\n' +
213
+ '\nPlease update the following components: %s',
214
+ sortedNames,
215
+ );
216
+ }
217
+
218
+ if (UNSAFE_componentWillUpdateUniqueNames.size > 0) {
219
+ const sortedNames = setToSortedString(
220
+ UNSAFE_componentWillUpdateUniqueNames,
221
+ );
222
+ console.error(
223
+ 'Using UNSAFE_componentWillUpdate in strict mode is not recommended ' +
224
+ 'and may indicate bugs in your code. ' +
225
+ 'See https://fb.me/react-unsafe-component-lifecycles for details.\n\n' +
226
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
227
+ '\nPlease update the following components: %s',
228
+ sortedNames,
229
+ );
230
+ }
231
+
232
+ if (componentWillMountUniqueNames.size > 0) {
233
+ const sortedNames = setToSortedString(componentWillMountUniqueNames);
234
+
235
+ console.warn(
236
+ 'componentWillMount has been renamed, and is not recommended for use. ' +
237
+ 'See https://fb.me/react-unsafe-component-lifecycles for details.\n\n' +
238
+ '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
239
+ '* Rename componentWillMount to UNSAFE_componentWillMount to suppress ' +
240
+ 'this warning in non-strict mode. In React 17.x, only the UNSAFE_ name will work. ' +
241
+ 'To rename all deprecated lifecycles to their new names, you can run ' +
242
+ '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
243
+ '\nPlease update the following components: %s',
244
+ sortedNames,
245
+ );
246
+ }
247
+
248
+ if (componentWillReceivePropsUniqueNames.size > 0) {
249
+ const sortedNames = setToSortedString(
250
+ componentWillReceivePropsUniqueNames,
251
+ );
252
+
253
+ console.warn(
254
+ 'componentWillReceiveProps has been renamed, and is not recommended for use. ' +
255
+ 'See https://fb.me/react-unsafe-component-lifecycles for details.\n\n' +
256
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
257
+ "* If you're updating state whenever props change, refactor your " +
258
+ 'code to use memoization techniques or move it to ' +
259
+ 'static getDerivedStateFromProps. Learn more at: https://fb.me/react-derived-state\n' +
260
+ '* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress ' +
261
+ 'this warning in non-strict mode. In React 17.x, only the UNSAFE_ name will work. ' +
262
+ 'To rename all deprecated lifecycles to their new names, you can run ' +
263
+ '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
264
+ '\nPlease update the following components: %s',
265
+ sortedNames,
266
+ );
267
+ }
268
+
269
+ if (componentWillUpdateUniqueNames.size > 0) {
270
+ const sortedNames = setToSortedString(componentWillUpdateUniqueNames);
271
+
272
+ console.warn(
273
+ 'componentWillUpdate has been renamed, and is not recommended for use. ' +
274
+ 'See https://fb.me/react-unsafe-component-lifecycles for details.\n\n' +
275
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
276
+ '* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress ' +
277
+ 'this warning in non-strict mode. In React 17.x, only the UNSAFE_ name will work. ' +
278
+ 'To rename all deprecated lifecycles to their new names, you can run ' +
279
+ '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
280
+ '\nPlease update the following components: %s',
281
+ sortedNames,
282
+ );
283
+ }
284
+ };
285
+
286
+ let pendingLegacyContextWarning: FiberToFiberComponentsMap = new Map();
287
+
288
+ // Tracks components we have already warned about.
289
+ const didWarnAboutLegacyContext = new Set();
290
+
291
+ ReactStrictModeWarnings.recordLegacyContextWarning = (
292
+ fiber: Fiber,
293
+ instance: any,
294
+ ) => {
295
+ const strictRoot = findStrictRoot(fiber);
296
+ if (strictRoot === null) {
297
+ console.error(
298
+ 'Expected to find a StrictMode component in a strict mode tree. ' +
299
+ 'This error is likely caused by a bug in React. Please file an issue.',
300
+ );
301
+ return;
302
+ }
303
+
304
+ // Dedup strategy: Warn once per component.
305
+ if (didWarnAboutLegacyContext.has(fiber.type)) {
306
+ return;
307
+ }
308
+
309
+ let warningsForRoot = pendingLegacyContextWarning.get(strictRoot);
310
+
311
+ if (
312
+ fiber.type.contextTypes != null ||
313
+ fiber.type.childContextTypes != null ||
314
+ (instance !== null && typeof instance.getChildContext === 'function')
315
+ ) {
316
+ if (warningsForRoot === undefined) {
317
+ warningsForRoot = [];
318
+ pendingLegacyContextWarning.set(strictRoot, warningsForRoot);
319
+ }
320
+ warningsForRoot.push(fiber);
321
+ }
322
+ };
323
+
324
+ ReactStrictModeWarnings.flushLegacyContextWarning = () => {
325
+ ((pendingLegacyContextWarning: any): FiberToFiberComponentsMap).forEach(
326
+ (fiberArray: FiberArray, strictRoot) => {
327
+ if (fiberArray.length === 0) {
328
+ return;
329
+ }
330
+ const firstFiber = fiberArray[0];
331
+
332
+ const uniqueNames = new Set();
333
+ fiberArray.forEach(fiber => {
334
+ uniqueNames.add(getComponentName(fiber.type) || 'Component');
335
+ didWarnAboutLegacyContext.add(fiber.type);
336
+ });
337
+
338
+ const sortedNames = setToSortedString(uniqueNames);
339
+ const firstComponentStack = getStackByFiberInDevAndProd(firstFiber);
340
+
341
+ console.error(
342
+ 'Legacy context API has been detected within a strict-mode tree.' +
343
+ '\n\nThe old API will be supported in all 16.x releases, but applications ' +
344
+ 'using it should migrate to the new version.' +
345
+ '\n\nPlease update the following components: %s' +
346
+ '\n\nLearn more about this warning here: https://fb.me/react-legacy-context' +
347
+ '%s',
348
+ sortedNames,
349
+ firstComponentStack,
350
+ );
351
+ },
352
+ );
353
+ };
354
+
355
+ ReactStrictModeWarnings.discardPendingWarnings = () => {
356
+ pendingComponentWillMountWarnings = [];
357
+ pendingUNSAFE_ComponentWillMountWarnings = [];
358
+ pendingComponentWillReceivePropsWarnings = [];
359
+ pendingUNSAFE_ComponentWillReceivePropsWarnings = [];
360
+ pendingComponentWillUpdateWarnings = [];
361
+ pendingUNSAFE_ComponentWillUpdateWarnings = [];
362
+ pendingLegacyContextWarning = new Map();
363
+ };
364
+}
365
+
366
+export default ReactStrictModeWarnings;
packages/react-reconciler/src/ReactUpdateQueue.new.js
new
+628
@@ -0,0 +1,628 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+// UpdateQueue is a linked list of prioritized updates.
11
+//
12
+// Like fibers, update queues come in pairs: a current queue, which represents
13
+// the visible state of the screen, and a work-in-progress queue, which can be
14
+// mutated and processed asynchronously before it is committed — a form of
15
+// double buffering. If a work-in-progress render is discarded before finishing,
16
+// we create a new work-in-progress by cloning the current queue.
17
+//
18
+// Both queues share a persistent, singly-linked list structure. To schedule an
19
+// update, we append it to the end of both queues. Each queue maintains a
20
+// pointer to first update in the persistent list that hasn't been processed.
21
+// The work-in-progress pointer always has a position equal to or greater than
22
+// the current queue, since we always work on that one. The current queue's
23
+// pointer is only updated during the commit phase, when we swap in the
24
+// work-in-progress.
25
+//
26
+// For example:
27
+//
28
+// Current pointer: A - B - C - D - E - F
29
+// Work-in-progress pointer: D - E - F
30
+// ^
31
+// The work-in-progress queue has
32
+// processed more updates than current.
33
+//
34
+// The reason we append to both queues is because otherwise we might drop
35
+// updates without ever processing them. For example, if we only add updates to
36
+// the work-in-progress queue, some updates could be lost whenever a work-in
37
+// -progress render restarts by cloning from current. Similarly, if we only add
38
+// updates to the current queue, the updates will be lost whenever an already
39
+// in-progress queue commits and swaps with the current queue. However, by
40
+// adding to both queues, we guarantee that the update will be part of the next
41
+// work-in-progress. (And because the work-in-progress queue becomes the
42
+// current queue once it commits, there's no danger of applying the same
43
+// update twice.)
44
+//
45
+// Prioritization
46
+// --------------
47
+//
48
+// Updates are not sorted by priority, but by insertion; new updates are always
49
+// appended to the end of the list.
50
+//
51
+// The priority is still important, though. When processing the update queue
52
+// during the render phase, only the updates with sufficient priority are
53
+// included in the result. If we skip an update because it has insufficient
54
+// priority, it remains in the queue to be processed later, during a lower
55
+// priority render. Crucially, all updates subsequent to a skipped update also
56
+// remain in the queue *regardless of their priority*. That means high priority
57
+// updates are sometimes processed twice, at two separate priorities. We also
58
+// keep track of a base state, that represents the state before the first
59
+// update in the queue is applied.
60
+//
61
+// For example:
62
+//
63
+// Given a base state of '', and the following queue of updates
64
+//
65
+// A1 - B2 - C1 - D2
66
+//
67
+// where the number indicates the priority, and the update is applied to the
68
+// previous state by appending a letter, React will process these updates as
69
+// two separate renders, one per distinct priority level:
70
+//
71
+// First render, at priority 1:
72
+// Base state: ''
73
+// Updates: [A1, C1]
74
+// Result state: 'AC'
75
+//
76
+// Second render, at priority 2:
77
+// Base state: 'A' <- The base state does not include C1,
78
+// because B2 was skipped.
79
+// Updates: [B2, C1, D2] <- C1 was rebased on top of B2
80
+// Result state: 'ABCD'
81
+//
82
+// Because we process updates in insertion order, and rebase high priority
83
+// updates when preceding updates are skipped, the final result is deterministic
84
+// regardless of priority. Intermediate state may vary according to system
85
+// resources, but the final state is always the same.
86
+
87
+import type {Fiber} from './ReactInternalTypes';
88
+import type {ExpirationTime} from './ReactFiberExpirationTime';
89
+import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
90
+import type {ReactPriorityLevel} from './ReactInternalTypes';
91
+
92
+import {NoWork, Sync} from './ReactFiberExpirationTime';
93
+import {
94
+ enterDisallowedContextReadInDEV,
95
+ exitDisallowedContextReadInDEV,
96
+} from './ReactFiberNewContext.new';
97
+import {Callback, ShouldCapture, DidCapture} from './ReactSideEffectTags';
98
+
99
+import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags';
100
+
101
+import {StrictMode} from './ReactTypeOfMode';
102
+import {
103
+ markRenderEventTimeAndConfig,
104
+ markUnprocessedUpdateTime,
105
+} from './ReactFiberWorkLoop.new';
106
+
107
+import invariant from 'shared/invariant';
108
+import {getCurrentPriorityLevel} from './SchedulerWithReactIntegration.new';
109
+
110
+import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
111
+
112
+export type Update<State> = {|
113
+ expirationTime: ExpirationTime,
114
+ suspenseConfig: null | SuspenseConfig,
115
+
116
+ tag: 0 | 1 | 2 | 3,
117
+ payload: any,
118
+ callback: (() => mixed) | null,
119
+
120
+ next: Update<State> | null,
121
+
122
+ // DEV only
123
+ priority?: ReactPriorityLevel,
124
+|};
125
+
126
+type SharedQueue<State> = {|pending: Update<State> | null|};
127
+
128
+export type UpdateQueue<State> = {|
129
+ baseState: State,
130
+ firstBaseUpdate: Update<State> | null,
131
+ lastBaseUpdate: Update<State> | null,
132
+ shared: SharedQueue<State>,
133
+ effects: Array<Update<State>> | null,
134
+|};
135
+
136
+export const UpdateState = 0;
137
+export const ReplaceState = 1;
138
+export const ForceUpdate = 2;
139
+export const CaptureUpdate = 3;
140
+
141
+// Global state that is reset at the beginning of calling `processUpdateQueue`.
142
+// It should only be read right after calling `processUpdateQueue`, via
143
+// `checkHasForceUpdateAfterProcessing`.
144
+let hasForceUpdate = false;
145
+
146
+let didWarnUpdateInsideUpdate;
147
+let currentlyProcessingQueue;
148
+export let resetCurrentlyProcessingQueue;
149
+if (__DEV__) {
150
+ didWarnUpdateInsideUpdate = false;
151
+ currentlyProcessingQueue = null;
152
+ resetCurrentlyProcessingQueue = () => {
153
+ currentlyProcessingQueue = null;
154
+ };
155
+}
156
+
157
+export function initializeUpdateQueue<State>(fiber: Fiber): void {
158
+ const queue: UpdateQueue<State> = {
159
+ baseState: fiber.memoizedState,
160
+ firstBaseUpdate: null,
161
+ lastBaseUpdate: null,
162
+ shared: {
163
+ pending: null,
164
+ },
165
+ effects: null,
166
+ };
167
+ fiber.updateQueue = queue;
168
+}
169
+
170
+export function cloneUpdateQueue<State>(
171
+ current: Fiber,
172
+ workInProgress: Fiber,
173
+): void {
174
+ // Clone the update queue from current. Unless it's already a clone.
175
+ const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
176
+ const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
177
+ if (queue === currentQueue) {
178
+ const clone: UpdateQueue<State> = {
179
+ baseState: currentQueue.baseState,
180
+ firstBaseUpdate: currentQueue.firstBaseUpdate,
181
+ lastBaseUpdate: currentQueue.lastBaseUpdate,
182
+ shared: currentQueue.shared,
183
+ effects: currentQueue.effects,
184
+ };
185
+ workInProgress.updateQueue = clone;
186
+ }
187
+}
188
+
189
+export function createUpdate(
190
+ expirationTime: ExpirationTime,
191
+ suspenseConfig: null | SuspenseConfig,
192
+): Update<*> {
193
+ const update: Update<*> = {
194
+ expirationTime,
195
+ suspenseConfig,
196
+
197
+ tag: UpdateState,
198
+ payload: null,
199
+ callback: null,
200
+
201
+ next: null,
202
+ };
203
+ if (__DEV__) {
204
+ update.priority = getCurrentPriorityLevel();
205
+ }
206
+ return update;
207
+}
208
+
209
+export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
210
+ const updateQueue = fiber.updateQueue;
211
+ if (updateQueue === null) {
212
+ // Only occurs if the fiber has been unmounted.
213
+ return;
214
+ }
215
+
216
+ const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
217
+ const pending = sharedQueue.pending;
218
+ if (pending === null) {
219
+ // This is the first update. Create a circular list.
220
+ update.next = update;
221
+ } else {
222
+ update.next = pending.next;
223
+ pending.next = update;
224
+ }
225
+ sharedQueue.pending = update;
226
+
227
+ if (__DEV__) {
228
+ if (
229
+ currentlyProcessingQueue === sharedQueue &&
230
+ !didWarnUpdateInsideUpdate
231
+ ) {
232
+ console.error(
233
+ 'An update (setState, replaceState, or forceUpdate) was scheduled ' +
234
+ 'from inside an update function. Update functions should be pure, ' +
235
+ 'with zero side-effects. Consider using componentDidUpdate or a ' +
236
+ 'callback.',
237
+ );
238
+ didWarnUpdateInsideUpdate = true;
239
+ }
240
+ }
241
+}
242
+
243
+export function enqueueCapturedUpdate<State>(
244
+ workInProgress: Fiber,
245
+ capturedUpdate: Update<State>,
246
+) {
247
+ // Captured updates are updates that are thrown by a child during the render
248
+ // phase. They should be discarded if the render is aborted. Therefore,
249
+ // we should only put them on the work-in-progress queue, not the current one.
250
+ let queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
251
+
252
+ // Check if the work-in-progress queue is a clone.
253
+ const current = workInProgress.alternate;
254
+ if (current !== null) {
255
+ const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
256
+ if (queue === currentQueue) {
257
+ // The work-in-progress queue is the same as current. This happens when
258
+ // we bail out on a parent fiber that then captures an error thrown by
259
+ // a child. Since we want to append the update only to the work-in
260
+ // -progress queue, we need to clone the updates. We usually clone during
261
+ // processUpdateQueue, but that didn't happen in this case because we
262
+ // skipped over the parent when we bailed out.
263
+ let newFirst = null;
264
+ let newLast = null;
265
+ const firstBaseUpdate = queue.firstBaseUpdate;
266
+ if (firstBaseUpdate !== null) {
267
+ // Loop through the updates and clone them.
268
+ let update = firstBaseUpdate;
269
+ do {
270
+ const clone: Update<State> = {
271
+ expirationTime: update.expirationTime,
272
+ suspenseConfig: update.suspenseConfig,
273
+
274
+ tag: update.tag,
275
+ payload: update.payload,
276
+ callback: update.callback,
277
+
278
+ next: null,
279
+ };
280
+ if (newLast === null) {
281
+ newFirst = newLast = clone;
282
+ } else {
283
+ newLast.next = clone;
284
+ newLast = clone;
285
+ }
286
+ update = update.next;
287
+ } while (update !== null);
288
+
289
+ // Append the captured update the end of the cloned list.
290
+ if (newLast === null) {
291
+ newFirst = newLast = capturedUpdate;
292
+ } else {
293
+ newLast.next = capturedUpdate;
294
+ newLast = capturedUpdate;
295
+ }
296
+ } else {
297
+ // There are no base updates.
298
+ newFirst = newLast = capturedUpdate;
299
+ }
300
+ queue = {
301
+ baseState: currentQueue.baseState,
302
+ firstBaseUpdate: newFirst,
303
+ lastBaseUpdate: newLast,
304
+ shared: currentQueue.shared,
305
+ effects: currentQueue.effects,
306
+ };
307
+ workInProgress.updateQueue = queue;
308
+ return;
309
+ }
310
+ }
311
+
312
+ // Append the update to the end of the list.
313
+ const lastBaseUpdate = queue.lastBaseUpdate;
314
+ if (lastBaseUpdate === null) {
315
+ queue.firstBaseUpdate = capturedUpdate;
316
+ } else {
317
+ lastBaseUpdate.next = capturedUpdate;
318
+ }
319
+ queue.lastBaseUpdate = capturedUpdate;
320
+}
321
+
322
+function getStateFromUpdate<State>(
323
+ workInProgress: Fiber,
324
+ queue: UpdateQueue<State>,
325
+ update: Update<State>,
326
+ prevState: State,
327
+ nextProps: any,
328
+ instance: any,
329
+): any {
330
+ switch (update.tag) {
331
+ case ReplaceState: {
332
+ const payload = update.payload;
333
+ if (typeof payload === 'function') {
334
+ // Updater function
335
+ if (__DEV__) {
336
+ enterDisallowedContextReadInDEV();
337
+ }
338
+ const nextState = payload.call(instance, prevState, nextProps);
339
+ if (__DEV__) {
340
+ if (
341
+ debugRenderPhaseSideEffectsForStrictMode &&
342
+ workInProgress.mode & StrictMode
343
+ ) {
344
+ disableLogs();
345
+ try {
346
+ payload.call(instance, prevState, nextProps);
347
+ } finally {
348
+ reenableLogs();
349
+ }
350
+ }
351
+ exitDisallowedContextReadInDEV();
352
+ }
353
+ return nextState;
354
+ }
355
+ // State object
356
+ return payload;
357
+ }
358
+ case CaptureUpdate: {
359
+ workInProgress.effectTag =
360
+ (workInProgress.effectTag & ~ShouldCapture) | DidCapture;
361
+ }
362
+ // Intentional fallthrough
363
+ case UpdateState: {
364
+ const payload = update.payload;
365
+ let partialState;
366
+ if (typeof payload === 'function') {
367
+ // Updater function
368
+ if (__DEV__) {
369
+ enterDisallowedContextReadInDEV();
370
+ }
371
+ partialState = payload.call(instance, prevState, nextProps);
372
+ if (__DEV__) {
373
+ if (
374
+ debugRenderPhaseSideEffectsForStrictMode &&
375
+ workInProgress.mode & StrictMode
376
+ ) {
377
+ disableLogs();
378
+ try {
379
+ payload.call(instance, prevState, nextProps);
380
+ } finally {
381
+ reenableLogs();
382
+ }
383
+ }
384
+ exitDisallowedContextReadInDEV();
385
+ }
386
+ } else {
387
+ // Partial state object
388
+ partialState = payload;
389
+ }
390
+ if (partialState === null || partialState === undefined) {
391
+ // Null and undefined are treated as no-ops.
392
+ return prevState;
393
+ }
394
+ // Merge the partial state and the previous state.
395
+ return Object.assign({}, prevState, partialState);
396
+ }
397
+ case ForceUpdate: {
398
+ hasForceUpdate = true;
399
+ return prevState;
400
+ }
401
+ }
402
+ return prevState;
403
+}
404
+
405
+export function processUpdateQueue<State>(
406
+ workInProgress: Fiber,
407
+ props: any,
408
+ instance: any,
409
+ renderExpirationTime: ExpirationTime,
410
+): void {
411
+ // This is always non-null on a ClassComponent or HostRoot
412
+ const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
413
+
414
+ hasForceUpdate = false;
415
+
416
+ if (__DEV__) {
417
+ currentlyProcessingQueue = queue.shared;
418
+ }
419
+
420
+ let firstBaseUpdate = queue.firstBaseUpdate;
421
+ let lastBaseUpdate = queue.lastBaseUpdate;
422
+
423
+ // Check if there are pending updates. If so, transfer them to the base queue.
424
+ let pendingQueue = queue.shared.pending;
425
+ if (pendingQueue !== null) {
426
+ queue.shared.pending = null;
427
+
428
+ // The pending queue is circular. Disconnect the pointer between first
429
+ // and last so that it's non-circular.
430
+ const lastPendingUpdate = pendingQueue;
431
+ const firstPendingUpdate = lastPendingUpdate.next;
432
+ lastPendingUpdate.next = null;
433
+ // Append pending updates to base queue
434
+ if (lastBaseUpdate === null) {
435
+ firstBaseUpdate = firstPendingUpdate;
436
+ } else {
437
+ lastBaseUpdate.next = firstPendingUpdate;
438
+ }
439
+ lastBaseUpdate = lastPendingUpdate;
440
+
441
+ // If there's a current queue, and it's different from the base queue, then
442
+ // we need to transfer the updates to that queue, too. Because the base
443
+ // queue is a singly-linked list with no cycles, we can append to both
444
+ // lists and take advantage of structural sharing.
445
+ // TODO: Pass `current` as argument
446
+ const current = workInProgress.alternate;
447
+ if (current !== null) {
448
+ // This is always non-null on a ClassComponent or HostRoot
449
+ const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
450
+ const currentLastBaseUpdate = currentQueue.lastBaseUpdate;
451
+ if (currentLastBaseUpdate !== lastBaseUpdate) {
452
+ if (currentLastBaseUpdate === null) {
453
+ currentQueue.firstBaseUpdate = firstPendingUpdate;
454
+ } else {
455
+ currentLastBaseUpdate.next = firstPendingUpdate;
456
+ }
457
+ currentQueue.lastBaseUpdate = lastPendingUpdate;
458
+ }
459
+ }
460
+ }
461
+
462
+ // These values may change as we process the queue.
463
+ if (firstBaseUpdate !== null) {
464
+ // Iterate through the list of updates to compute the result.
465
+ let newState = queue.baseState;
466
+ let newExpirationTime = NoWork;
467
+
468
+ let newBaseState = null;
469
+ let newFirstBaseUpdate = null;
470
+ let newLastBaseUpdate = null;
471
+
472
+ let update = firstBaseUpdate;
473
+ do {
474
+ const updateExpirationTime = update.expirationTime;
475
+ if (updateExpirationTime < renderExpirationTime) {
476
+ // Priority is insufficient. Skip this update. If this is the first
477
+ // skipped update, the previous update/state is the new base
478
+ // update/state.
479
+ const clone: Update<State> = {
480
+ expirationTime: update.expirationTime,
481
+ suspenseConfig: update.suspenseConfig,
482
+
483
+ tag: update.tag,
484
+ payload: update.payload,
485
+ callback: update.callback,
486
+
487
+ next: null,
488
+ };
489
+ if (newLastBaseUpdate === null) {
490
+ newFirstBaseUpdate = newLastBaseUpdate = clone;
491
+ newBaseState = newState;
492
+ } else {
493
+ newLastBaseUpdate = newLastBaseUpdate.next = clone;
494
+ }
495
+ // Update the remaining priority in the queue.
496
+ if (updateExpirationTime > newExpirationTime) {
497
+ newExpirationTime = updateExpirationTime;
498
+ }
499
+ } else {
500
+ // This update does have sufficient priority.
501
+
502
+ if (newLastBaseUpdate !== null) {
503
+ const clone: Update<State> = {
504
+ expirationTime: Sync, // This update is going to be committed so we never want uncommit it.
505
+ suspenseConfig: update.suspenseConfig,
506
+
507
+ tag: update.tag,
508
+ payload: update.payload,
509
+ callback: update.callback,
510
+
511
+ next: null,
512
+ };
513
+ newLastBaseUpdate = newLastBaseUpdate.next = clone;
514
+ }
515
+
516
+ // Mark the event time of this update as relevant to this render pass.
517
+ // TODO: This should ideally use the true event time of this update rather than
518
+ // its priority which is a derived and not reverseable value.
519
+ // TODO: We should skip this update if it was already committed but currently
520
+ // we have no way of detecting the difference between a committed and suspended
521
+ // update here.
522
+ markRenderEventTimeAndConfig(
523
+ updateExpirationTime,
524
+ update.suspenseConfig,
525
+ );
526
+
527
+ // Process this update.
528
+ newState = getStateFromUpdate(
529
+ workInProgress,
530
+ queue,
531
+ update,
532
+ newState,
533
+ props,
534
+ instance,
535
+ );
536
+ const callback = update.callback;
537
+ if (callback !== null) {
538
+ workInProgress.effectTag |= Callback;
539
+ const effects = queue.effects;
540
+ if (effects === null) {
541
+ queue.effects = [update];
542
+ } else {
543
+ effects.push(update);
544
+ }
545
+ }
546
+ }
547
+ update = update.next;
548
+ if (update === null) {
549
+ pendingQueue = queue.shared.pending;
550
+ if (pendingQueue === null) {
551
+ break;
552
+ } else {
553
+ // An update was scheduled from inside a reducer. Add the new
554
+ // pending updates to the end of the list and keep processing.
555
+ const lastPendingUpdate = pendingQueue;
556
+ // Intentionally unsound. Pending updates form a circular list, but we
557
+ // unravel them when transferring them to the base queue.
558
+ const firstPendingUpdate = ((lastPendingUpdate.next: any): Update<State>);
559
+ lastPendingUpdate.next = null;
560
+ update = firstPendingUpdate;
561
+ queue.lastBaseUpdate = lastPendingUpdate;
562
+ queue.shared.pending = null;
563
+ }
564
+ }
565
+ } while (true);
566
+
567
+ if (newLastBaseUpdate === null) {
568
+ newBaseState = newState;
569
+ }
570
+
571
+ queue.baseState = ((newBaseState: any): State);
572
+ queue.firstBaseUpdate = newFirstBaseUpdate;
573
+ queue.lastBaseUpdate = newLastBaseUpdate;
574
+
575
+ // Set the remaining expiration time to be whatever is remaining in the queue.
576
+ // This should be fine because the only two other things that contribute to
577
+ // expiration time are props and context. We're already in the middle of the
578
+ // begin phase by the time we start processing the queue, so we've already
579
+ // dealt with the props. Context in components that specify
580
+ // shouldComponentUpdate is tricky; but we'll have to account for
581
+ // that regardless.
582
+ markUnprocessedUpdateTime(newExpirationTime);
583
+ workInProgress.expirationTime = newExpirationTime;
584
+ workInProgress.memoizedState = newState;
585
+ }
586
+
587
+ if (__DEV__) {
588
+ currentlyProcessingQueue = null;
589
+ }
590
+}
591
+
592
+function callCallback(callback, context) {
593
+ invariant(
594
+ typeof callback === 'function',
595
+ 'Invalid argument passed as callback. Expected a function. Instead ' +
596
+ 'received: %s',
597
+ callback,
598
+ );
599
+ callback.call(context);
600
+}
601
+
602
+export function resetHasForceUpdateBeforeProcessing() {
603
+ hasForceUpdate = false;
604
+}
605
+
606
+export function checkHasForceUpdateAfterProcessing(): boolean {
607
+ return hasForceUpdate;
608
+}
609
+
610
+export function commitUpdateQueue<State>(
611
+ finishedWork: Fiber,
612
+ finishedQueue: UpdateQueue<State>,
613
+ instance: any,
614
+): void {
615
+ // Commit the effects
616
+ const effects = finishedQueue.effects;
617
+ finishedQueue.effects = null;
618
+ if (effects !== null) {
619
+ for (let i = 0; i < effects.length; i++) {
620
+ const effect = effects[i];
621
+ const callback = effect.callback;
622
+ if (callback !== null) {
623
+ effect.callback = null;
624
+ callCallback(callback, instance);
625
+ }
626
+ }
627
+ }
628
+}
packages/react-reconciler/src/SchedulerWithReactIntegration.new.js
new
+201
@@ -0,0 +1,201 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {ReactPriorityLevel} from './ReactInternalTypes';
11
+
12
+// Intentionally not named imports because Rollup would use dynamic dispatch for
13
+// CommonJS interop named imports.
14
+import * as Scheduler from 'scheduler';
15
+import {__interactionsRef} from 'scheduler/tracing';
16
+import {enableSchedulerTracing} from 'shared/ReactFeatureFlags';
17
+import invariant from 'shared/invariant';
18
+
19
+const {
20
+ unstable_runWithPriority: Scheduler_runWithPriority,
21
+ unstable_scheduleCallback: Scheduler_scheduleCallback,
22
+ unstable_cancelCallback: Scheduler_cancelCallback,
23
+ unstable_shouldYield: Scheduler_shouldYield,
24
+ unstable_requestPaint: Scheduler_requestPaint,
25
+ unstable_now: Scheduler_now,
26
+ unstable_getCurrentPriorityLevel: Scheduler_getCurrentPriorityLevel,
27
+ unstable_ImmediatePriority: Scheduler_ImmediatePriority,
28
+ unstable_UserBlockingPriority: Scheduler_UserBlockingPriority,
29
+ unstable_NormalPriority: Scheduler_NormalPriority,
30
+ unstable_LowPriority: Scheduler_LowPriority,
31
+ unstable_IdlePriority: Scheduler_IdlePriority,
32
+} = Scheduler;
33
+
34
+if (enableSchedulerTracing) {
35
+ // Provide explicit error message when production+profiling bundle of e.g.
36
+ // react-dom is used with production (non-profiling) bundle of
37
+ // scheduler/tracing
38
+ invariant(
39
+ __interactionsRef != null && __interactionsRef.current != null,
40
+ 'It is not supported to run the profiling version of a renderer (for ' +
41
+ 'example, `react-dom/profiling`) without also replacing the ' +
42
+ '`scheduler/tracing` module with `scheduler/tracing-profiling`. Your ' +
43
+ 'bundler might have a setting for aliasing both modules. Learn more at ' +
44
+ 'http://fb.me/react-profiling',
45
+ );
46
+}
47
+
48
+export type SchedulerCallback = (isSync: boolean) => SchedulerCallback | null;
49
+
50
+type SchedulerCallbackOptions = {timeout?: number, ...};
51
+
52
+const fakeCallbackNode = {};
53
+
54
+// Except for NoPriority, these correspond to Scheduler priorities. We use
55
+// ascending numbers so we can compare them like numbers. They start at 90 to
56
+// avoid clashing with Scheduler's priorities.
57
+export const ImmediatePriority: ReactPriorityLevel = 99;
58
+export const UserBlockingPriority: ReactPriorityLevel = 98;
59
+export const NormalPriority: ReactPriorityLevel = 97;
60
+export const LowPriority: ReactPriorityLevel = 96;
61
+export const IdlePriority: ReactPriorityLevel = 95;
62
+// NoPriority is the absence of priority. Also React-only.
63
+export const NoPriority: ReactPriorityLevel = 90;
64
+
65
+export const shouldYield = Scheduler_shouldYield;
66
+export const requestPaint =
67
+ // Fall back gracefully if we're running an older version of Scheduler.
68
+ Scheduler_requestPaint !== undefined ? Scheduler_requestPaint : () => {};
69
+
70
+let syncQueue: Array<SchedulerCallback> | null = null;
71
+let immediateQueueCallbackNode: mixed | null = null;
72
+let isFlushingSyncQueue: boolean = false;
73
+const initialTimeMs: number = Scheduler_now();
74
+
75
+// If the initial timestamp is reasonably small, use Scheduler's `now` directly.
76
+// This will be the case for modern browsers that support `performance.now`. In
77
+// older browsers, Scheduler falls back to `Date.now`, which returns a Unix
78
+// timestamp. In that case, subtract the module initialization time to simulate
79
+// the behavior of performance.now and keep our times small enough to fit
80
+// within 32 bits.
81
+// TODO: Consider lifting this into Scheduler.
82
+export const now =
83
+ initialTimeMs < 10000 ? Scheduler_now : () => Scheduler_now() - initialTimeMs;
84
+
85
+export function getCurrentPriorityLevel(): ReactPriorityLevel {
86
+ switch (Scheduler_getCurrentPriorityLevel()) {
87
+ case Scheduler_ImmediatePriority:
88
+ return ImmediatePriority;
89
+ case Scheduler_UserBlockingPriority:
90
+ return UserBlockingPriority;
91
+ case Scheduler_NormalPriority:
92
+ return NormalPriority;
93
+ case Scheduler_LowPriority:
94
+ return LowPriority;
95
+ case Scheduler_IdlePriority:
96
+ return IdlePriority;
97
+ default:
98
+ invariant(false, 'Unknown priority level.');
99
+ }
100
+}
101
+
102
+function reactPriorityToSchedulerPriority(reactPriorityLevel) {
103
+ switch (reactPriorityLevel) {
104
+ case ImmediatePriority:
105
+ return Scheduler_ImmediatePriority;
106
+ case UserBlockingPriority:
107
+ return Scheduler_UserBlockingPriority;
108
+ case NormalPriority:
109
+ return Scheduler_NormalPriority;
110
+ case LowPriority:
111
+ return Scheduler_LowPriority;
112
+ case IdlePriority:
113
+ return Scheduler_IdlePriority;
114
+ default:
115
+ invariant(false, 'Unknown priority level.');
116
+ }
117
+}
118
+
119
+export function runWithPriority<T>(
120
+ reactPriorityLevel: ReactPriorityLevel,
121
+ fn: () => T,
122
+): T {
123
+ const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
124
+ return Scheduler_runWithPriority(priorityLevel, fn);
125
+}
126
+
127
+export function scheduleCallback(
128
+ reactPriorityLevel: ReactPriorityLevel,
129
+ callback: SchedulerCallback,
130
+ options: SchedulerCallbackOptions | void | null,
131
+) {
132
+ const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
133
+ return Scheduler_scheduleCallback(priorityLevel, callback, options);
134
+}
135
+
136
+export function scheduleSyncCallback(callback: SchedulerCallback) {
137
+ // Push this callback into an internal queue. We'll flush these either in
138
+ // the next tick, or earlier if something calls `flushSyncCallbackQueue`.
139
+ if (syncQueue === null) {
140
+ syncQueue = [callback];
141
+ // Flush the queue in the next tick, at the earliest.
142
+ immediateQueueCallbackNode = Scheduler_scheduleCallback(
143
+ Scheduler_ImmediatePriority,
144
+ flushSyncCallbackQueueImpl,
145
+ );
146
+ } else {
147
+ // Push onto existing queue. Don't need to schedule a callback because
148
+ // we already scheduled one when we created the queue.
149
+ syncQueue.push(callback);
150
+ }
151
+ return fakeCallbackNode;
152
+}
153
+
154
+export function cancelCallback(callbackNode: mixed) {
155
+ if (callbackNode !== fakeCallbackNode) {
156
+ Scheduler_cancelCallback(callbackNode);
157
+ }
158
+}
159
+
160
+export function flushSyncCallbackQueue() {
161
+ if (immediateQueueCallbackNode !== null) {
162
+ const node = immediateQueueCallbackNode;
163
+ immediateQueueCallbackNode = null;
164
+ Scheduler_cancelCallback(node);
165
+ }
166
+ flushSyncCallbackQueueImpl();
167
+}
168
+
169
+function flushSyncCallbackQueueImpl() {
170
+ if (!isFlushingSyncQueue && syncQueue !== null) {
171
+ // Prevent re-entrancy.
172
+ isFlushingSyncQueue = true;
173
+ let i = 0;
174
+ try {
175
+ const isSync = true;
176
+ const queue = syncQueue;
177
+ runWithPriority(ImmediatePriority, () => {
178
+ for (; i < queue.length; i++) {
179
+ let callback = queue[i];
180
+ do {
181
+ callback = callback(isSync);
182
+ } while (callback !== null);
183
+ }
184
+ });
185
+ syncQueue = null;
186
+ } catch (error) {
187
+ // If something throws, leave the remaining callbacks on the queue.
188
+ if (syncQueue !== null) {
189
+ syncQueue = syncQueue.slice(i + 1);
190
+ }
191
+ // Resume flushing in the next tick
192
+ Scheduler_scheduleCallback(
193
+ Scheduler_ImmediatePriority,
194
+ flushSyncCallbackQueue,
195
+ );
196
+ throw error;
197
+ } finally {
198
+ isFlushingSyncQueue = false;
199
+ }
200
+ }
201
+}