Opt into unsafe lifecycle warnings without async tree (#12083)
Added new StrictMode component for enabling async warnings (without enabling async rendering). This component can be used in the future to help with other warnings (eg compilation, Fabric).
Brian Vaughn committed
Jan 24, 2018 at 17:49 UTC
be51e6a41c096f33acf92a693026b80c98e7ddeb
13 files changed
+278
-69
packages/react-reconciler/src/ReactFiber.js
+17
-2
@@ -26,15 +26,21 @@ import {
26
CallComponent,
27
ReturnComponent,
28
Fragment,
29
+ Mode,
30
} from 'shared/ReactTypeOfWork';
31
import getComponentName from 'shared/getComponentName';
32
33
import {NoWork} from './ReactFiberExpirationTime';
33
-import {NoContext, AsyncUpdates} from './ReactTypeOfInternalContext';
34
+import {
35
+ NoContext,
36
+ AsyncUpdates,
37
+ StrictMode,
38
+} from './ReactTypeOfInternalContext';
39
import {
40
REACT_FRAGMENT_TYPE,
41
REACT_RETURN_TYPE,
42
REACT_CALL_TYPE,
43
+ REACT_STRICT_MODE_TYPE,
44
} from 'shared/ReactSymbols';
45
46
let hasBadMapPolyfill;
@@ -293,7 +299,7 @@ export function createWorkInProgress(
299
}
300
301
export function createHostRootFiber(isAsync): Fiber {
296
- const internalContextTag = isAsync ? AsyncUpdates : NoContext;
302
+ const internalContextTag = isAsync ? AsyncUpdates | StrictMode : NoContext;
303
return createFiber(HostRoot, null, null, internalContextTag);
304
}
305
@@ -333,6 +339,15 @@ export function createFiberFromElement(
339
expirationTime,
340
key,
341
);
342
+ case REACT_STRICT_MODE_TYPE:
343
+ fiber = createFiber(
344
+ Mode,
345
+ pendingProps,
346
+ key,
347
+ internalContextTag | StrictMode,
348
+ );
349
+ fiber.type = REACT_STRICT_MODE_TYPE;
350
+ break;
351
case REACT_CALL_TYPE:
352
fiber = createFiber(
353
CallComponent,
packages/react-reconciler/src/ReactFiberBeginWork.js
+19
@@ -26,6 +26,7 @@ import {
26
CallHandlerPhase,
27
ReturnComponent,
28
Fragment,
29
+ Mode,
30
} from 'shared/ReactTypeOfWork';
31
import {
32
PerformedWork,
@@ -160,6 +161,22 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
161
return workInProgress.child;
162
}
163
164
+ function updateMode(current, workInProgress) {
165
+ const nextChildren = workInProgress.pendingProps.children;
166
+ if (hasContextChanged()) {
167
+ // Normally we can bail out on props equality but if context has changed
168
+ // we don't do the bailout and we have to reuse existing props instead.
169
+ } else if (
170
+ nextChildren === null ||
171
+ workInProgress.memoizedProps === nextChildren
172
+ ) {
173
+ return bailoutOnAlreadyFinishedWork(current, workInProgress);
174
+ }
175
+ reconcileChildren(current, workInProgress, nextChildren);
176
+ memoizeProps(workInProgress, nextChildren);
177
+ return workInProgress.child;
178
+ }
179
+
180
function markRef(current: Fiber | null, workInProgress: Fiber) {
181
const ref = workInProgress.ref;
182
if (ref !== null && (!current || current.ref !== ref)) {
@@ -777,6 +794,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
794
);
795
case Fragment:
796
return updateFragment(current, workInProgress);
797
+ case Mode:
798
+ return updateMode(current, workInProgress);
799
default:
800
invariant(
801
false,
packages/react-reconciler/src/ReactFiberClassComponent.js
+13
-13
@@ -16,7 +16,7 @@ import {
16
enableAsyncSubtreeAPI,
17
warnAboutDeprecatedLifecycles,
18
} from 'shared/ReactFeatureFlags';
19
-import ReactDebugAsyncWarnings from './ReactDebugAsyncWarnings';
19
+import ReactStrictModeWarnings from './ReactStrictModeWarnings';
20
import {isMounted} from 'react-reconciler/reflection';
21
import * as ReactInstanceMap from 'shared/ReactInstanceMap';
22
import emptyObject from 'fbjs/lib/emptyObject';
@@ -26,7 +26,7 @@ import invariant from 'fbjs/lib/invariant';
26
import warning from 'fbjs/lib/warning';
27
28
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
29
-import {AsyncUpdates} from './ReactTypeOfInternalContext';
29
+import {AsyncUpdates, StrictMode} from './ReactTypeOfInternalContext';
30
import {
31
cacheContext,
32
getMaskedContext,
@@ -639,20 +639,20 @@ export default function(
639
instance.refs = emptyObject;
640
instance.context = getMaskedContext(workInProgress, unmaskedContext);
641
642
- if (
643
- enableAsyncSubtreeAPI &&
644
- workInProgress.type != null &&
645
- workInProgress.type.prototype != null &&
646
- workInProgress.type.prototype.unstable_isAsyncReactComponent === true
647
- ) {
648
- workInProgress.internalContextTag |= AsyncUpdates;
642
+ if (workInProgress.type != null && workInProgress.type.prototype != null) {
643
+ const prototype = workInProgress.type.prototype;
644
+
645
+ if (enableAsyncSubtreeAPI) {
646
+ if (prototype.unstable_isAsyncReactComponent === true) {
647
+ workInProgress.internalContextTag |= AsyncUpdates;
648
+ workInProgress.internalContextTag |= StrictMode;
649
+ }
650
+ }
651
}
652
653
if (__DEV__) {
652
- // If we're inside of an async sub-tree,
653
- // Warn about any unsafe lifecycles on this class component.
654
- if (workInProgress.internalContextTag & AsyncUpdates) {
655
- ReactDebugAsyncWarnings.recordLifecycleWarnings(
654
+ if (workInProgress.internalContextTag & StrictMode) {
655
+ ReactStrictModeWarnings.recordLifecycleWarnings(
656
workInProgress,
657
instance,
658
);
packages/react-reconciler/src/ReactFiberCompleteWork.js
+3
@@ -31,6 +31,7 @@ import {
31
CallHandlerPhase,
32
ReturnComponent,
33
Fragment,
34
+ Mode,
35
} from 'shared/ReactTypeOfWork';
36
import {Placement, Ref, Update} from 'shared/ReactTypeOfSideEffect';
37
import invariant from 'fbjs/lib/invariant';
@@ -576,6 +577,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
577
return null;
578
case Fragment:
579
return null;
580
+ case Mode:
581
+ return null;
582
case HostPortal:
583
popHostContainer(workInProgress);
584
updateHostContainer(workInProgress);
packages/react-reconciler/src/ReactFiberScheduler.js
+3
-3
@@ -16,7 +16,7 @@ import type {ExpirationTime} from './ReactFiberExpirationTime';
16
import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
17
import ReactErrorUtils from 'shared/ReactErrorUtils';
18
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
19
-import ReactDebugAsyncWarnings from './ReactDebugAsyncWarnings';
19
+import ReactStrictModeWarnings from './ReactStrictModeWarnings';
20
import {
21
PerformedWork,
22
Placement,
@@ -312,7 +312,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
312
313
function commitAllLifeCycles() {
314
if (__DEV__) {
315
- ReactDebugAsyncWarnings.flushPendingAsyncWarnings();
315
+ ReactStrictModeWarnings.flushPendingAsyncWarnings();
316
}
317
318
while (nextEffect !== null) {
@@ -657,7 +657,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
657
658
function performFailedUnitOfWork(workInProgress: Fiber): Fiber | null {
659
if (__DEV__) {
660
- ReactDebugAsyncWarnings.discardPendingWarnings();
660
+ ReactStrictModeWarnings.discardPendingWarnings();
661
}
662
663
// The current, flushed, state of this fiber is the alternate.
packages/react-reconciler/src/ReactStrictModeWarnings.js
renamed
+21
-21
@@ -11,7 +11,7 @@ import type {Fiber} from './ReactFiber';
11
12
import getComponentName from 'shared/getComponentName';
13
import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
14
-import {AsyncUpdates} from './ReactTypeOfInternalContext';
14
+import {StrictMode} from './ReactTypeOfInternalContext';
15
import warning from 'fbjs/lib/warning';
16
17
type LIFECYCLE =
@@ -21,7 +21,7 @@ type LIFECYCLE =
21
type LifecycleToComponentsMap = {[lifecycle: LIFECYCLE]: Array<Fiber>};
22
type FiberToLifecycleMap = Map<Fiber, LifecycleToComponentsMap>;
23
24
-const ReactDebugAsyncWarnings = {
24
+const ReactStrictModeWarnings = {
25
discardPendingWarnings(): void {},
26
flushPendingAsyncWarnings(): void {},
27
recordLifecycleWarnings(fiber: Fiber, instance: any): void {},
@@ -39,13 +39,13 @@ if (__DEV__) {
39
// Tracks components we have already warned about.
40
const didWarnSet = new Set();
41
42
- ReactDebugAsyncWarnings.discardPendingWarnings = () => {
42
+ ReactStrictModeWarnings.discardPendingWarnings = () => {
43
pendingWarningsMap = new Map();
44
};
45
46
- ReactDebugAsyncWarnings.flushPendingAsyncWarnings = () => {
46
+ ReactStrictModeWarnings.flushPendingAsyncWarnings = () => {
47
((pendingWarningsMap: any): FiberToLifecycleMap).forEach(
48
- (lifecycleWarningsMap, asyncRoot) => {
48
+ (lifecycleWarningsMap, strictRoot) => {
49
const lifecyclesWarningMesages = [];
50
51
Object.keys(lifecycleWarningsMap).forEach(lifecycle => {
@@ -71,17 +71,17 @@ if (__DEV__) {
71
});
72
73
if (lifecyclesWarningMesages.length > 0) {
74
- const asyncRootComponentStack = getStackAddendumByWorkInProgressFiber(
75
- asyncRoot,
74
+ const strictRootComponentStack = getStackAddendumByWorkInProgressFiber(
75
+ strictRoot,
76
);
77
78
warning(
79
false,
80
- 'Unsafe lifecycle methods were found within the following async tree:%s' +
80
+ 'Unsafe lifecycle methods were found within a strict-mode tree:%s' +
81
'\n\n%s' +
82
'\n\nLearn more about this warning here:' +
83
- '\nhttps://fb.me/react-async-component-lifecycle-hooks',
84
- asyncRootComponentStack,
83
+ '\nhttps://fb.me/react-strict-mode-warnings',
84
+ strictRootComponentStack,
85
lifecyclesWarningMesages.join('\n\n'),
86
);
87
}
@@ -91,25 +91,25 @@ if (__DEV__) {
91
pendingWarningsMap = new Map();
92
};
93
94
- const getAsyncRoot = (fiber: Fiber): Fiber => {
95
- let maybeAsyncRoot = null;
94
+ const getStrictRoot = (fiber: Fiber): Fiber => {
95
+ let maybeStrictRoot = null;
96
97
while (fiber !== null) {
98
- if (fiber.internalContextTag & AsyncUpdates) {
99
- maybeAsyncRoot = fiber;
98
+ if (fiber.internalContextTag & StrictMode) {
99
+ maybeStrictRoot = fiber;
100
}
101
102
fiber = fiber.return;
103
}
104
105
- return maybeAsyncRoot;
105
+ return maybeStrictRoot;
106
};
107
108
- ReactDebugAsyncWarnings.recordLifecycleWarnings = (
108
+ ReactStrictModeWarnings.recordLifecycleWarnings = (
109
fiber: Fiber,
110
instance: any,
111
) => {
112
- const asyncRoot = getAsyncRoot(fiber);
112
+ const strictRoot = getStrictRoot(fiber);
113
114
// Dedup strategy: Warn once per component.
115
// This is difficult to track any other way since component names
@@ -121,16 +121,16 @@ if (__DEV__) {
121
}
122
123
let warningsForRoot;
124
- if (!pendingWarningsMap.has(asyncRoot)) {
124
+ if (!pendingWarningsMap.has(strictRoot)) {
125
warningsForRoot = {
126
UNSAFE_componentWillMount: [],
127
UNSAFE_componentWillReceiveProps: [],
128
UNSAFE_componentWillUpdate: [],
129
};
130
131
- pendingWarningsMap.set(asyncRoot, warningsForRoot);
131
+ pendingWarningsMap.set(strictRoot, warningsForRoot);
132
} else {
133
- warningsForRoot = pendingWarningsMap.get(asyncRoot);
133
+ warningsForRoot = pendingWarningsMap.get(strictRoot);
134
}
135
136
const unsafeLifecycles = [];
@@ -163,4 +163,4 @@ if (__DEV__) {
163
};
164
}
165
166
-export default ReactDebugAsyncWarnings;
166
+export default ReactStrictModeWarnings;
packages/react-reconciler/src/ReactTypeOfInternalContext.js
+3
-2
@@ -9,5 +9,6 @@
9
10
export type TypeOfInternalContext = number;
11
12
-export const NoContext = 0;
13
-export const AsyncUpdates = 1;
12
+export const NoContext = 0b00000000;
13
+export const AsyncUpdates = 0b00000001;
14
+export const StrictMode = 0b00000010;
packages/react/src/React.js
+3
-2
@@ -7,9 +7,9 @@
7
8
import assign from 'object-assign';
9
import ReactVersion from 'shared/ReactVersion';
10
-import {REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
10
+import {REACT_FRAGMENT_TYPE, REACT_STRICT_MODE_TYPE} from 'shared/ReactSymbols';
11
12
-import {Component, PureComponent, AsyncComponent} from './ReactBaseClasses';
12
+import {AsyncComponent, Component, PureComponent} from './ReactBaseClasses';
13
import {forEach, map, count, toArray, only} from './ReactChildren';
14
import ReactCurrentOwner from './ReactCurrentOwner';
15
import {
@@ -39,6 +39,7 @@ const React = {
39
unstable_AsyncComponent: AsyncComponent,
40
41
Fragment: REACT_FRAGMENT_TYPE,
42
+ StrictMode: REACT_STRICT_MODE_TYPE,
43
44
createElement: __DEV__ ? createElementWithValidation : createElement,
45
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
packages/react/src/ReactBaseClasses.js
+8
-10
@@ -117,34 +117,32 @@ if (__DEV__) {
117
}
118
}
119
120
+function ComponentDummy() {}
121
+ComponentDummy.prototype = Component.prototype;
122
+
123
/**
121
- * Base class helpers for the updating state of a component.
124
+ * Convenience component with default shallow equality check for sCU.
125
*/
126
function PureComponent(props, context, updater) {
124
- // Duplicated from Component.
127
this.props = props;
128
this.context = context;
129
this.refs = emptyObject;
128
- // We initialize the default updater but the real one gets injected by the
129
- // renderer.
130
this.updater = updater || ReactNoopUpdateQueue;
131
}
132
133
-function ComponentDummy() {}
134
-ComponentDummy.prototype = Component.prototype;
133
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
134
pureComponentPrototype.constructor = PureComponent;
135
// Avoid an extra prototype jump for these methods.
136
Object.assign(pureComponentPrototype, Component.prototype);
137
pureComponentPrototype.isPureReactComponent = true;
138
139
+/**
140
+ * Special component type that opts subtree into async rendering mode.
141
+ */
142
function AsyncComponent(props, context, updater) {
142
- // Duplicated from Component.
143
this.props = props;
144
this.context = context;
145
this.refs = emptyObject;
146
- // We initialize the default updater but the real one gets injected by the
147
- // renderer.
146
this.updater = updater || ReactNoopUpdateQueue;
147
}
148
@@ -157,4 +155,4 @@ asyncComponentPrototype.render = function() {
155
return this.props.children;
156
};
157
160
-export {Component, PureComponent, AsyncComponent};
158
+export {AsyncComponent, Component, PureComponent};
packages/react/src/ReactElementValidator.js
+7
-2
@@ -15,7 +15,11 @@
15
import lowPriorityWarning from 'shared/lowPriorityWarning';
16
import describeComponentFrame from 'shared/describeComponentFrame';
17
import getComponentName from 'shared/getComponentName';
18
-import {getIteratorFn, REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
18
+import {
19
+ getIteratorFn,
20
+ REACT_FRAGMENT_TYPE,
21
+ REACT_STRICT_MODE_TYPE,
22
+} from 'shared/ReactSymbols';
23
import checkPropTypes from 'prop-types/checkPropTypes';
24
import warning from 'fbjs/lib/warning';
25
@@ -282,7 +286,8 @@ export function createElementWithValidation(type, props, children) {
286
typeof type === 'string' ||
287
typeof type === 'function' ||
288
// Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill.
285
- type === REACT_FRAGMENT_TYPE;
289
+ type === REACT_FRAGMENT_TYPE ||
290
+ type === REACT_STRICT_MODE_TYPE;
291
292
// We warn in this case but don't throw. We expect the element creation to
293
// succeed and there will likely be errors in render.
packages/react/src/__tests__/ReactStrictMode-test.internal.js
renamed
+176
-13
@@ -13,7 +13,7 @@ let React;
13
let ReactFeatureFlags;
14
let ReactTestRenderer;
15
16
-describe('ReactAsyncClassComponent', () => {
16
+describe('ReactStrictMode', () => {
17
describe('debugRenderPhaseSideEffects', () => {
18
beforeEach(() => {
19
jest.resetModules();
@@ -194,7 +194,7 @@ describe('ReactAsyncClassComponent', () => {
194
expect(() => {
195
rendered = ReactTestRenderer.create(<SyncRoot />);
196
}).toWarnDev(
197
- 'Unsafe lifecycle methods were found within the following async tree:' +
197
+ 'Unsafe lifecycle methods were found within a strict-mode tree:' +
198
'\n in AsyncRoot (at **)' +
199
'\n in SyncRoot (at **)' +
200
'\n\ncomponentWillMount: Please update the following components ' +
@@ -204,7 +204,7 @@ describe('ReactAsyncClassComponent', () => {
204
'\n\ncomponentWillUpdate: Please update the following components ' +
205
'to use componentDidUpdate instead: AsyncRoot' +
206
'\n\nLearn more about this warning here:' +
207
- '\nhttps://fb.me/react-async-component-lifecycle-hooks',
207
+ '\nhttps://fb.me/react-strict-mode-warnings',
208
);
209
210
// Dedupe
@@ -248,7 +248,7 @@ describe('ReactAsyncClassComponent', () => {
248
expect(
249
() => (rendered = ReactTestRenderer.create(<SyncRoot />)),
250
).toWarnDev(
251
- 'Unsafe lifecycle methods were found within the following async tree:' +
251
+ 'Unsafe lifecycle methods were found within a strict-mode tree:' +
252
'\n in AsyncRoot (at **)' +
253
'\n in SyncRoot (at **)' +
254
'\n\ncomponentWillMount: Please update the following components ' +
@@ -258,7 +258,7 @@ describe('ReactAsyncClassComponent', () => {
258
'\n\ncomponentWillUpdate: Please update the following components ' +
259
'to use componentDidUpdate instead: AsyncRoot, Parent' +
260
'\n\nLearn more about this warning here:' +
261
- '\nhttps://fb.me/react-async-component-lifecycle-hooks',
261
+ '\nhttps://fb.me/react-strict-mode-warnings',
262
);
263
264
// Dedupe
@@ -322,13 +322,13 @@ describe('ReactAsyncClassComponent', () => {
322
expect(
323
() => (rendered = ReactTestRenderer.create(<SyncRoot />)),
324
).toWarnDev([
325
- 'Unsafe lifecycle methods were found within the following async tree:' +
325
+ 'Unsafe lifecycle methods were found within a strict-mode tree:' +
326
'\n in AsyncRootOne (at **)' +
327
'\n in div (at **)' +
328
'\n in SyncRoot (at **)' +
329
'\n\ncomponentWillMount: Please update the following components ' +
330
'to use componentDidMount instead: Bar, Foo',
331
- 'Unsafe lifecycle methods were found within the following async tree:' +
331
+ 'Unsafe lifecycle methods were found within a strict-mode tree:' +
332
'\n in AsyncRootTwo (at **)' +
333
'\n in div (at **)' +
334
'\n in SyncRoot (at **)' +
@@ -364,21 +364,21 @@ describe('ReactAsyncClassComponent', () => {
364
expect(() => {
365
rendered = ReactTestRenderer.create(<AsyncRoot foo={true} />);
366
}).toWarnDev(
367
- 'Unsafe lifecycle methods were found within the following async tree:' +
367
+ 'Unsafe lifecycle methods were found within a strict-mode tree:' +
368
'\n in AsyncRoot (at **)' +
369
'\n\ncomponentWillMount: Please update the following components ' +
370
'to use componentDidMount instead: Foo' +
371
'\n\nLearn more about this warning here:' +
372
- '\nhttps://fb.me/react-async-component-lifecycle-hooks',
372
+ '\nhttps://fb.me/react-strict-mode-warnings',
373
);
374
375
expect(() => rendered.update(<AsyncRoot foo={false} />)).toWarnDev(
376
- 'Unsafe lifecycle methods were found within the following async tree:' +
376
+ 'Unsafe lifecycle methods were found within a strict-mode tree:' +
377
'\n in AsyncRoot (at **)' +
378
'\n\ncomponentWillMount: Please update the following components ' +
379
'to use componentDidMount instead: Bar' +
380
'\n\nLearn more about this warning here:' +
381
- '\nhttps://fb.me/react-async-component-lifecycle-hooks',
381
+ '\nhttps://fb.me/react-strict-mode-warnings',
382
);
383
384
// Dedupe
@@ -422,15 +422,178 @@ describe('ReactAsyncClassComponent', () => {
422
expect(() => {
423
ReactTestRenderer.create(<AsyncRoot foo={true} />);
424
}).toWarnDev(
425
- 'Unsafe lifecycle methods were found within the following async tree:' +
425
+ 'Unsafe lifecycle methods were found within a strict-mode tree:' +
426
'\n in AsyncRoot (at **)' +
427
'\n\ncomponentWillMount: Please update the following components ' +
428
'to use componentDidMount instead: Bar' +
429
'\n\nLearn more about this warning here:' +
430
- '\nhttps://fb.me/react-async-component-lifecycle-hooks',
430
+ '\nhttps://fb.me/react-strict-mode-warnings',
431
);
432
433
expect(caughtError).not.toBe(null);
434
});
435
+
436
+ it('should also warn inside of "strict" mode trees', () => {
437
+ const {StrictMode} = React;
438
+
439
+ class SyncRoot extends React.Component {
440
+ UNSAFE_componentWillMount() {}
441
+ UNSAFE_componentWillUpdate() {}
442
+ UNSAFE_componentWillReceiveProps() {}
443
+ render() {
444
+ return (
445
+ <StrictMode>
446
+ <Wrapper />
447
+ </StrictMode>
448
+ );
449
+ }
450
+ }
451
+ function Wrapper({children}) {
452
+ return (
453
+ <div>
454
+ <Bar />
455
+ <Foo />
456
+ </div>
457
+ );
458
+ }
459
+ class Foo extends React.Component {
460
+ UNSAFE_componentWillReceiveProps() {}
461
+ render() {
462
+ return null;
463
+ }
464
+ }
465
+ class Bar extends React.Component {
466
+ UNSAFE_componentWillReceiveProps() {}
467
+ render() {
468
+ return null;
469
+ }
470
+ }
471
+
472
+ expect(() => ReactTestRenderer.create(<SyncRoot />)).toWarnDev(
473
+ 'Unsafe lifecycle methods were found within a strict-mode tree:' +
474
+ '\n in SyncRoot (at **)' +
475
+ '\n\ncomponentWillReceiveProps: Please update the following components ' +
476
+ 'to use static getDerivedStateFromProps instead: Bar, Foo' +
477
+ '\n\nLearn more about this warning here:' +
478
+ '\nhttps://fb.me/react-strict-mode-warnings',
479
+ );
480
+
481
+ // Dedupe
482
+ const rendered = ReactTestRenderer.create(<SyncRoot />);
483
+ rendered.update(<SyncRoot />);
484
+ });
485
+ });
486
+
487
+ describe('symbol checks', () => {
488
+ beforeEach(() => {
489
+ jest.resetModules();
490
+ React = require('react');
491
+ ReactTestRenderer = require('react-test-renderer');
492
+ });
493
+
494
+ it('should switch from StrictMode to a Fragment and reset state', () => {
495
+ const {Fragment, StrictMode} = React;
496
+
497
+ function ParentComponent({useFragment}) {
498
+ return useFragment ? (
499
+ <Fragment>
500
+ <ChildComponent />
501
+ </Fragment>
502
+ ) : (
503
+ <StrictMode>
504
+ <ChildComponent />
505
+ </StrictMode>
506
+ );
507
+ }
508
+
509
+ class ChildComponent extends React.Component {
510
+ state = {
511
+ count: 0,
512
+ };
513
+ static getDerivedStateFromProps(nextProps, prevState) {
514
+ return {
515
+ count: prevState.count + 1,
516
+ };
517
+ }
518
+ render() {
519
+ return `count:${this.state.count}`;
520
+ }
521
+ }
522
+
523
+ const rendered = ReactTestRenderer.create(
524
+ <ParentComponent useFragment={false} />,
525
+ );
526
+ expect(rendered.toJSON()).toBe('count:1');
527
+ rendered.update(<ParentComponent useFragment={true} />);
528
+ expect(rendered.toJSON()).toBe('count:1');
529
+ });
530
+
531
+ it('should switch from a Fragment to StrictMode and reset state', () => {
532
+ const {Fragment, StrictMode} = React;
533
+
534
+ function ParentComponent({useFragment}) {
535
+ return useFragment ? (
536
+ <Fragment>
537
+ <ChildComponent />
538
+ </Fragment>
539
+ ) : (
540
+ <StrictMode>
541
+ <ChildComponent />
542
+ </StrictMode>
543
+ );
544
+ }
545
+
546
+ class ChildComponent extends React.Component {
547
+ state = {
548
+ count: 0,
549
+ };
550
+ static getDerivedStateFromProps(nextProps, prevState) {
551
+ return {
552
+ count: prevState.count + 1,
553
+ };
554
+ }
555
+ render() {
556
+ return `count:${this.state.count}`;
557
+ }
558
+ }
559
+
560
+ const rendered = ReactTestRenderer.create(
561
+ <ParentComponent useFragment={true} />,
562
+ );
563
+ expect(rendered.toJSON()).toBe('count:1');
564
+ rendered.update(<ParentComponent useFragment={false} />);
565
+ expect(rendered.toJSON()).toBe('count:1');
566
+ });
567
+
568
+ it('should update with StrictMode without losing state', () => {
569
+ const {StrictMode} = React;
570
+
571
+ function ParentComponent() {
572
+ return (
573
+ <StrictMode>
574
+ <ChildComponent />
575
+ </StrictMode>
576
+ );
577
+ }
578
+
579
+ class ChildComponent extends React.Component {
580
+ state = {
581
+ count: 0,
582
+ };
583
+ static getDerivedStateFromProps(nextProps, prevState) {
584
+ return {
585
+ count: prevState.count + 1,
586
+ };
587
+ }
588
+ render() {
589
+ return `count:${this.state.count}`;
590
+ }
591
+ }
592
+
593
+ const rendered = ReactTestRenderer.create(<ParentComponent />);
594
+ expect(rendered.toJSON()).toBe('count:1');
595
+ rendered.update(<ParentComponent />);
596
+ expect(rendered.toJSON()).toBe('count:2');
597
+ });
598
});
599
});
packages/shared/ReactSymbols.js
+3
@@ -24,6 +24,9 @@ export const REACT_PORTAL_TYPE = hasSymbol
24
export const REACT_FRAGMENT_TYPE = hasSymbol
25
? Symbol.for('react.fragment')
26
: 0xeacb;
27
+export const REACT_STRICT_MODE_TYPE = hasSymbol
28
+ ? Symbol.for('react.strict_mode')
29
+ : 0xeacc;
30
31
const MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
32
const FAUX_ITERATOR_SYMBOL = '@@iterator';
packages/shared/ReactTypeOfWork.js
+2
-1
@@ -7,7 +7,7 @@
7
* @flow
8
*/
9
10
-export type TypeOfWork = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
10
+export type TypeOfWork = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11;
11
12
export const IndeterminateComponent = 0; // Before we know whether it is functional or class
13
export const FunctionalComponent = 1;
@@ -20,3 +20,4 @@ export const CallComponent = 7;
20
export const CallHandlerPhase = 8;
21
export const ReturnComponent = 9;
22
export const Fragment = 10;
23
+export const Mode = 11;