@samitouri / QOS-React-2 / commits / 17f582e045

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 +}