@samitouri / QOS-React / commits / be51e6a41c

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;