Re-enable Flow for ReactFiber and fix Flow issues (#12842)
* Lint for untyped imports and enable Flow typing in ReactFiber * Re-enable Flow for ReactFiber and fix Flow issues * Avoid an invariant in DEV-only code I just introduced it, but on a second thought, it's better to keep it as a warning. * Address review
Dan Abramov committed
May 17, 2018 at 17:14 UTC
b245795de373ca8f4a6c4962b59773e2f1bb2f1d
7 files changed
+120
-108
.flowconfig
+3
@@ -21,6 +21,9 @@
21
./node_modules/fbjs/flow/lib/dev.js
22
./scripts/flow
23
24
+[lints]
25
+untyped-type-import=error
26
+
27
[options]
28
esproposal.class_static_fields=enable
29
esproposal.class_instance_fields=enable
packages/react-dom/src/events/ReactDOMEventListener.js
+1
-1
@@ -54,7 +54,7 @@ function getTopLevelCallbackBookKeeping(
54
): {
55
topLevelType: ?DOMTopLevelEventType,
56
nativeEvent: ?AnyNativeEvent,
57
- targetInst: Fiber,
57
+ targetInst: Fiber | null,
58
ancestors: Array<Fiber>,
59
} {
60
if (callbackBookkeepingPool.length) {
packages/react-reconciler/src/ReactCapturedValue.js
+1
-1
@@ -29,7 +29,7 @@ export type CapturedError = {
29
30
export function createCapturedValue<T>(
31
value: T,
32
- source: Fiber | null,
32
+ source: Fiber,
33
): CapturedValue<T> {
34
// If the value is an error, call this function immediately after it is thrown
35
// so the stack is accurate.
packages/react-reconciler/src/ReactFiber.js
+45
-63
@@ -4,10 +4,11 @@
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, Source} from 'shared/ReactElementType';
10
-import type {ReactPortal, RefObject} from 'shared/ReactTypes';
11
+import type {ReactFragment, ReactPortal, RefObject} from 'shared/ReactTypes';
12
import type {TypeOfWork} from 'shared/ReactTypeOfWork';
13
import type {TypeOfMode} from './ReactTypeOfMode';
14
import type {TypeOfSideEffect} from 'shared/ReactTypeOfSideEffect';
@@ -316,7 +317,7 @@ export function createWorkInProgress(
317
return workInProgress;
318
}
319
319
-export function createHostRootFiber(isAsync): Fiber {
320
+export function createHostRootFiber(isAsync: boolean): Fiber {
321
const mode = isAsync ? AsyncMode | StrictMode : NoContext;
322
return createFiber(HostRoot, null, null, mode);
323
}
@@ -366,42 +367,9 @@ export function createFiberFromElement(
367
// mode compatible.
368
mode |= StrictMode;
369
break;
369
- default: {
370
- if (typeof type === 'object' && type !== null) {
371
- switch (type.$$typeof) {
372
- case REACT_PROVIDER_TYPE:
373
- fiberTag = ContextProvider;
374
- break;
375
- case REACT_CONTEXT_TYPE:
376
- // This is a consumer
377
- fiberTag = ContextConsumer;
378
- break;
379
- case REACT_FORWARD_REF_TYPE:
380
- fiberTag = ForwardRef;
381
- break;
382
- default:
383
- if (typeof type.tag === 'number') {
384
- // Currently assumed to be a continuation and therefore is a
385
- // fiber already.
386
- // TODO: The yield system is currently broken for updates in
387
- // some cases. The reified yield stores a fiber, but we don't
388
- // know which fiber that is; the current or a workInProgress?
389
- // When the continuation gets rendered here we don't know if we
390
- // can reuse that fiber or if we need to clone it. There is
391
- // probably a clever way to restructure this.
392
- fiber = ((type: any): Fiber);
393
- fiber.pendingProps = pendingProps;
394
- fiber.expirationTime = expirationTime;
395
- return fiber;
396
- } else {
397
- throwOnInvalidElementType(type, owner);
398
- }
399
- break;
400
- }
401
- } else {
402
- throwOnInvalidElementType(type, owner);
403
- }
404
- }
370
+ default:
371
+ fiberTag = getFiberTagFromObjectType(type, owner);
372
+ break;
373
}
374
}
375
@@ -417,33 +385,47 @@ export function createFiberFromElement(
385
return fiber;
386
}
387
420
-function throwOnInvalidElementType(type, owner) {
421
- let info = '';
422
- if (__DEV__) {
423
- if (
424
- type === undefined ||
425
- (typeof type === 'object' &&
426
- type !== null &&
427
- Object.keys(type).length === 0)
428
- ) {
429
- info +=
430
- ' You likely forgot to export your component from the file ' +
431
- "it's defined in, or you might have mixed up default and " +
432
- 'named imports.';
433
- }
434
- const ownerName = owner ? getComponentName(owner) : null;
435
- if (ownerName) {
436
- info += '\n\nCheck the render method of `' + ownerName + '`.';
388
+function getFiberTagFromObjectType(type, owner): TypeOfWork {
389
+ const $$typeof =
390
+ typeof type === 'object' && type !== null ? type.$$typeof : null;
391
+
392
+ switch ($$typeof) {
393
+ case REACT_PROVIDER_TYPE:
394
+ return ContextProvider;
395
+ case REACT_CONTEXT_TYPE:
396
+ // This is a consumer
397
+ return ContextConsumer;
398
+ case REACT_FORWARD_REF_TYPE:
399
+ return ForwardRef;
400
+ default: {
401
+ let info = '';
402
+ if (__DEV__) {
403
+ if (
404
+ type === undefined ||
405
+ (typeof type === 'object' &&
406
+ type !== null &&
407
+ Object.keys(type).length === 0)
408
+ ) {
409
+ info +=
410
+ ' You likely forgot to export your component from the file ' +
411
+ "it's defined in, or you might have mixed up default and " +
412
+ 'named imports.';
413
+ }
414
+ const ownerName = owner ? getComponentName(owner) : null;
415
+ if (ownerName) {
416
+ info += '\n\nCheck the render method of `' + ownerName + '`.';
417
+ }
418
+ }
419
+ invariant(
420
+ false,
421
+ 'Element type is invalid: expected a string (for built-in ' +
422
+ 'components) or a class/function (for composite components) ' +
423
+ 'but got: %s.%s',
424
+ type == null ? type : typeof type,
425
+ info,
426
+ );
427
}
428
}
439
- invariant(
440
- false,
441
- 'Element type is invalid: expected a string (for built-in ' +
442
- 'components) or a class/function (for composite components) ' +
443
- 'but got: %s.%s',
444
- type == null ? type : typeof type,
445
- info,
446
- );
429
}
430
431
export function createFiberFromFragment(
packages/react-reconciler/src/ReactFiberCommitWork.js
+3
-3
@@ -9,7 +9,7 @@
9
10
import type {HostConfig} from 'react-reconciler';
11
import type {Fiber} from './ReactFiber';
12
-import type {FiberRoot} from './ReactFiber';
12
+import type {FiberRoot} from './ReactFiberRoot';
13
import type {ExpirationTime} from './ReactFiberExpirationTime';
14
import type {CapturedValue, CapturedError} from './ReactCapturedValue';
15
@@ -59,7 +59,7 @@ if (__DEV__) {
59
export function logError(boundary: Fiber, errorInfo: CapturedValue<mixed>) {
60
const source = errorInfo.source;
61
let stack = errorInfo.stack;
62
- if (stack === null) {
62
+ if (stack === null && source !== null) {
63
stack = getStackAddendumByWorkInProgressFiber(source);
64
}
65
@@ -94,7 +94,7 @@ export function logError(boundary: Fiber, errorInfo: CapturedValue<mixed>) {
94
95
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
96
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
97
- captureError: (failedFiber: Fiber, error: mixed) => Fiber | null,
97
+ captureError: (failedFiber: Fiber, error: mixed) => void,
98
scheduleWork: (
99
fiber: Fiber,
100
startTime: ExpirationTime,
packages/react-reconciler/src/ReactFiberScheduler.js
+52
-33
@@ -321,10 +321,20 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
321
}
322
323
// Restore the original state of the work-in-progress
324
+ if (stashedWorkInProgressProperties === null) {
325
+ // This should never happen. Don't throw because this code is DEV-only.
326
+ warning(
327
+ false,
328
+ 'Could not replay rendering after an error. This is likely a bug in React. ' +
329
+ 'Please file an issue.',
330
+ );
331
+ return;
332
+ }
333
assignFiberPropertiesInDEV(
334
failedUnitOfWork,
335
stashedWorkInProgressProperties,
336
);
337
+
338
switch (failedUnitOfWork.tag) {
339
case HostRoot:
340
popHostContainer(failedUnitOfWork);
@@ -1073,43 +1083,52 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
1083
// This is a fatal error.
1084
didFatal = true;
1085
onUncaughtError(thrownValue);
1076
- break;
1077
- }
1086
+ } else {
1087
+ if (__DEV__) {
1088
+ // Reset global debug state
1089
+ // We assume this is defined in DEV
1090
+ (resetCurrentlyProcessingQueue: any)();
1091
+ }
1092
1079
- if (__DEV__) {
1080
- // Reset global debug state
1081
- // We assume this is defined in DEV
1082
- (resetCurrentlyProcessingQueue: any)();
1083
- }
1093
+ const failedUnitOfWork: Fiber = nextUnitOfWork;
1094
+ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1095
+ replayUnitOfWork(failedUnitOfWork, thrownValue, isAsync);
1096
+ }
1097
1085
- if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1086
- const failedUnitOfWork = nextUnitOfWork;
1087
- replayUnitOfWork(failedUnitOfWork, thrownValue, isAsync);
1088
- }
1098
+ // TODO: we already know this isn't true in some cases.
1099
+ // At least this shows a nicer error message until we figure out the cause.
1100
+ // https://github.com/facebook/react/issues/12449#issuecomment-386727431
1101
+ invariant(
1102
+ nextUnitOfWork !== null,
1103
+ 'Failed to replay rendering after an error. This ' +
1104
+ 'is likely caused by a bug in React. Please file an issue ' +
1105
+ 'with a reproducing case to help us find it.',
1106
+ );
1107
1090
- const sourceFiber: Fiber = nextUnitOfWork;
1091
- let returnFiber = sourceFiber.return;
1092
- if (returnFiber === null) {
1093
- // This is the root. The root could capture its own errors. However,
1094
- // we don't know if it errors before or after we pushed the host
1095
- // context. This information is needed to avoid a stack mismatch.
1096
- // Because we're not sure, treat this as a fatal error. We could track
1097
- // which phase it fails in, but doesn't seem worth it. At least
1098
- // for now.
1099
- didFatal = true;
1100
- onUncaughtError(thrownValue);
1101
- break;
1108
+ const sourceFiber: Fiber = nextUnitOfWork;
1109
+ let returnFiber = sourceFiber.return;
1110
+ if (returnFiber === null) {
1111
+ // This is the root. The root could capture its own errors. However,
1112
+ // we don't know if it errors before or after we pushed the host
1113
+ // context. This information is needed to avoid a stack mismatch.
1114
+ // Because we're not sure, treat this as a fatal error. We could track
1115
+ // which phase it fails in, but doesn't seem worth it. At least
1116
+ // for now.
1117
+ didFatal = true;
1118
+ onUncaughtError(thrownValue);
1119
+ break;
1120
+ }
1121
+ throwException(
1122
+ root,
1123
+ returnFiber,
1124
+ sourceFiber,
1125
+ thrownValue,
1126
+ nextRenderIsExpired,
1127
+ nextRenderExpirationTime,
1128
+ mostRecentCurrentTimeMs,
1129
+ );
1130
+ nextUnitOfWork = completeUnitOfWork(sourceFiber);
1131
}
1103
- throwException(
1104
- root,
1105
- returnFiber,
1106
- sourceFiber,
1107
- thrownValue,
1108
- nextRenderIsExpired,
1109
- nextRenderExpirationTime,
1110
- mostRecentCurrentTimeMs,
1111
- );
1112
- nextUnitOfWork = completeUnitOfWork(sourceFiber);
1132
}
1133
break;
1134
} while (true);
packages/react-reconciler/src/ReactStrictModeWarnings.js
+15
-7
@@ -107,15 +107,15 @@ if (__DEV__) {
107
pendingUnsafeLifecycleWarnings = new Map();
108
};
109
110
- const getStrictRoot = (fiber: Fiber): Fiber => {
110
+ const findStrictRoot = (fiber: Fiber): Fiber | null => {
111
let maybeStrictRoot = null;
112
113
- while (fiber !== null) {
114
- if (fiber.mode & StrictMode) {
115
- maybeStrictRoot = fiber;
113
+ let node = fiber;
114
+ while (node !== null) {
115
+ if (node.mode & StrictMode) {
116
+ maybeStrictRoot = node;
117
}
117
-
118
- fiber = fiber.return;
118
+ node = node.return;
119
}
120
121
return maybeStrictRoot;
@@ -225,7 +225,15 @@ if (__DEV__) {
225
fiber: Fiber,
226
instance: any,
227
) => {
228
- const strictRoot = getStrictRoot(fiber);
228
+ const strictRoot = findStrictRoot(fiber);
229
+ if (strictRoot === null) {
230
+ warning(
231
+ false,
232
+ 'Expected to find a StrictMode component in a strict mode tree. ' +
233
+ 'This error is likely caused by a bug in React. Please file an issue.',
234
+ );
235
+ return;
236
+ }
237
238
// Dedup strategy: Warn once per component.
239
// This is difficult to track any other way since component names