diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 9d7c305d6fcfb..d36433099c982 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -26,15 +26,21 @@ import { CallComponent, ReturnComponent, Fragment, + Mode, } from 'shared/ReactTypeOfWork'; import getComponentName from 'shared/getComponentName'; import {NoWork} from './ReactFiberExpirationTime'; -import {NoContext, AsyncUpdates} from './ReactTypeOfInternalContext'; +import { + NoContext, + AsyncUpdates, + StrictMode, +} from './ReactTypeOfInternalContext'; import { REACT_FRAGMENT_TYPE, REACT_RETURN_TYPE, REACT_CALL_TYPE, + REACT_STRICT_MODE_TYPE, } from 'shared/ReactSymbols'; let hasBadMapPolyfill; @@ -293,7 +299,7 @@ export function createWorkInProgress( } export function createHostRootFiber(isAsync): Fiber { - const internalContextTag = isAsync ? AsyncUpdates : NoContext; + const internalContextTag = isAsync ? AsyncUpdates | StrictMode : NoContext; return createFiber(HostRoot, null, null, internalContextTag); } @@ -333,6 +339,15 @@ export function createFiberFromElement( expirationTime, key, ); + case REACT_STRICT_MODE_TYPE: + fiber = createFiber( + Mode, + pendingProps, + key, + internalContextTag | StrictMode, + ); + fiber.type = REACT_STRICT_MODE_TYPE; + break; case REACT_CALL_TYPE: fiber = createFiber( CallComponent, diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 9b4569bf94892..d0569a6b4ad90 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -26,6 +26,7 @@ import { CallHandlerPhase, ReturnComponent, Fragment, + Mode, } from 'shared/ReactTypeOfWork'; import { PerformedWork, @@ -160,6 +161,22 @@ export default function( return workInProgress.child; } + function updateMode(current, workInProgress) { + const nextChildren = workInProgress.pendingProps.children; + if (hasContextChanged()) { + // Normally we can bail out on props equality but if context has changed + // we don't do the bailout and we have to reuse existing props instead. + } else if ( + nextChildren === null || + workInProgress.memoizedProps === nextChildren + ) { + return bailoutOnAlreadyFinishedWork(current, workInProgress); + } + reconcileChildren(current, workInProgress, nextChildren); + memoizeProps(workInProgress, nextChildren); + return workInProgress.child; + } + function markRef(current: Fiber | null, workInProgress: Fiber) { const ref = workInProgress.ref; if (ref !== null && (!current || current.ref !== ref)) { @@ -777,6 +794,8 @@ export default function( ); case Fragment: return updateFragment(current, workInProgress); + case Mode: + return updateMode(current, workInProgress); default: invariant( false, diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js index 9547c17267da2..8f8735ae8167f 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.js @@ -16,7 +16,7 @@ import { enableAsyncSubtreeAPI, warnAboutDeprecatedLifecycles, } from 'shared/ReactFeatureFlags'; -import ReactDebugAsyncWarnings from './ReactDebugAsyncWarnings'; +import ReactStrictModeWarnings from './ReactStrictModeWarnings'; import {isMounted} from 'react-reconciler/reflection'; import * as ReactInstanceMap from 'shared/ReactInstanceMap'; import emptyObject from 'fbjs/lib/emptyObject'; @@ -26,7 +26,7 @@ import invariant from 'fbjs/lib/invariant'; import warning from 'fbjs/lib/warning'; import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf'; -import {AsyncUpdates} from './ReactTypeOfInternalContext'; +import {AsyncUpdates, StrictMode} from './ReactTypeOfInternalContext'; import { cacheContext, getMaskedContext, @@ -639,20 +639,20 @@ export default function( instance.refs = emptyObject; instance.context = getMaskedContext(workInProgress, unmaskedContext); - if ( - enableAsyncSubtreeAPI && - workInProgress.type != null && - workInProgress.type.prototype != null && - workInProgress.type.prototype.unstable_isAsyncReactComponent === true - ) { - workInProgress.internalContextTag |= AsyncUpdates; + if (workInProgress.type != null && workInProgress.type.prototype != null) { + const prototype = workInProgress.type.prototype; + + if (enableAsyncSubtreeAPI) { + if (prototype.unstable_isAsyncReactComponent === true) { + workInProgress.internalContextTag |= AsyncUpdates; + workInProgress.internalContextTag |= StrictMode; + } + } } if (__DEV__) { - // If we're inside of an async sub-tree, - // Warn about any unsafe lifecycles on this class component. - if (workInProgress.internalContextTag & AsyncUpdates) { - ReactDebugAsyncWarnings.recordLifecycleWarnings( + if (workInProgress.internalContextTag & StrictMode) { + ReactStrictModeWarnings.recordLifecycleWarnings( workInProgress, instance, ); diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 123dee84ea250..d63f70ebbbb74 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -31,6 +31,7 @@ import { CallHandlerPhase, ReturnComponent, Fragment, + Mode, } from 'shared/ReactTypeOfWork'; import {Placement, Ref, Update} from 'shared/ReactTypeOfSideEffect'; import invariant from 'fbjs/lib/invariant'; @@ -576,6 +577,8 @@ export default function( return null; case Fragment: return null; + case Mode: + return null; case HostPortal: popHostContainer(workInProgress); updateHostContainer(workInProgress); diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index f6c8445232a2b..a2d0a5d354d59 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -16,7 +16,7 @@ import type {ExpirationTime} from './ReactFiberExpirationTime'; import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook'; import ReactErrorUtils from 'shared/ReactErrorUtils'; import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState'; -import ReactDebugAsyncWarnings from './ReactDebugAsyncWarnings'; +import ReactStrictModeWarnings from './ReactStrictModeWarnings'; import { PerformedWork, Placement, @@ -312,7 +312,7 @@ export default function( function commitAllLifeCycles() { if (__DEV__) { - ReactDebugAsyncWarnings.flushPendingAsyncWarnings(); + ReactStrictModeWarnings.flushPendingAsyncWarnings(); } while (nextEffect !== null) { @@ -657,7 +657,7 @@ export default function( function performFailedUnitOfWork(workInProgress: Fiber): Fiber | null { if (__DEV__) { - ReactDebugAsyncWarnings.discardPendingWarnings(); + ReactStrictModeWarnings.discardPendingWarnings(); } // The current, flushed, state of this fiber is the alternate. diff --git a/packages/react-reconciler/src/ReactDebugAsyncWarnings.js b/packages/react-reconciler/src/ReactStrictModeWarnings.js similarity index 79% rename from packages/react-reconciler/src/ReactDebugAsyncWarnings.js rename to packages/react-reconciler/src/ReactStrictModeWarnings.js index ec2acd96aafac..2249d677f90d9 100644 --- a/packages/react-reconciler/src/ReactDebugAsyncWarnings.js +++ b/packages/react-reconciler/src/ReactStrictModeWarnings.js @@ -11,7 +11,7 @@ import type {Fiber} from './ReactFiber'; import getComponentName from 'shared/getComponentName'; import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook'; -import {AsyncUpdates} from './ReactTypeOfInternalContext'; +import {StrictMode} from './ReactTypeOfInternalContext'; import warning from 'fbjs/lib/warning'; type LIFECYCLE = @@ -21,7 +21,7 @@ type LIFECYCLE = type LifecycleToComponentsMap = {[lifecycle: LIFECYCLE]: Array}; type FiberToLifecycleMap = Map; -const ReactDebugAsyncWarnings = { +const ReactStrictModeWarnings = { discardPendingWarnings(): void {}, flushPendingAsyncWarnings(): void {}, recordLifecycleWarnings(fiber: Fiber, instance: any): void {}, @@ -39,13 +39,13 @@ if (__DEV__) { // Tracks components we have already warned about. const didWarnSet = new Set(); - ReactDebugAsyncWarnings.discardPendingWarnings = () => { + ReactStrictModeWarnings.discardPendingWarnings = () => { pendingWarningsMap = new Map(); }; - ReactDebugAsyncWarnings.flushPendingAsyncWarnings = () => { + ReactStrictModeWarnings.flushPendingAsyncWarnings = () => { ((pendingWarningsMap: any): FiberToLifecycleMap).forEach( - (lifecycleWarningsMap, asyncRoot) => { + (lifecycleWarningsMap, strictRoot) => { const lifecyclesWarningMesages = []; Object.keys(lifecycleWarningsMap).forEach(lifecycle => { @@ -71,17 +71,17 @@ if (__DEV__) { }); if (lifecyclesWarningMesages.length > 0) { - const asyncRootComponentStack = getStackAddendumByWorkInProgressFiber( - asyncRoot, + const strictRootComponentStack = getStackAddendumByWorkInProgressFiber( + strictRoot, ); warning( false, - 'Unsafe lifecycle methods were found within the following async tree:%s' + + 'Unsafe lifecycle methods were found within a strict-mode tree:%s' + '\n\n%s' + '\n\nLearn more about this warning here:' + - '\nhttps://fb.me/react-async-component-lifecycle-hooks', - asyncRootComponentStack, + '\nhttps://fb.me/react-strict-mode-warnings', + strictRootComponentStack, lifecyclesWarningMesages.join('\n\n'), ); } @@ -91,25 +91,25 @@ if (__DEV__) { pendingWarningsMap = new Map(); }; - const getAsyncRoot = (fiber: Fiber): Fiber => { - let maybeAsyncRoot = null; + const getStrictRoot = (fiber: Fiber): Fiber => { + let maybeStrictRoot = null; while (fiber !== null) { - if (fiber.internalContextTag & AsyncUpdates) { - maybeAsyncRoot = fiber; + if (fiber.internalContextTag & StrictMode) { + maybeStrictRoot = fiber; } fiber = fiber.return; } - return maybeAsyncRoot; + return maybeStrictRoot; }; - ReactDebugAsyncWarnings.recordLifecycleWarnings = ( + ReactStrictModeWarnings.recordLifecycleWarnings = ( fiber: Fiber, instance: any, ) => { - const asyncRoot = getAsyncRoot(fiber); + const strictRoot = getStrictRoot(fiber); // Dedup strategy: Warn once per component. // This is difficult to track any other way since component names @@ -121,16 +121,16 @@ if (__DEV__) { } let warningsForRoot; - if (!pendingWarningsMap.has(asyncRoot)) { + if (!pendingWarningsMap.has(strictRoot)) { warningsForRoot = { UNSAFE_componentWillMount: [], UNSAFE_componentWillReceiveProps: [], UNSAFE_componentWillUpdate: [], }; - pendingWarningsMap.set(asyncRoot, warningsForRoot); + pendingWarningsMap.set(strictRoot, warningsForRoot); } else { - warningsForRoot = pendingWarningsMap.get(asyncRoot); + warningsForRoot = pendingWarningsMap.get(strictRoot); } const unsafeLifecycles = []; @@ -163,4 +163,4 @@ if (__DEV__) { }; } -export default ReactDebugAsyncWarnings; +export default ReactStrictModeWarnings; diff --git a/packages/react-reconciler/src/ReactTypeOfInternalContext.js b/packages/react-reconciler/src/ReactTypeOfInternalContext.js index 24859845f79f0..977dd8a413ea0 100644 --- a/packages/react-reconciler/src/ReactTypeOfInternalContext.js +++ b/packages/react-reconciler/src/ReactTypeOfInternalContext.js @@ -9,5 +9,6 @@ export type TypeOfInternalContext = number; -export const NoContext = 0; -export const AsyncUpdates = 1; +export const NoContext = 0b00000000; +export const AsyncUpdates = 0b00000001; +export const StrictMode = 0b00000010; diff --git a/packages/react/src/React.js b/packages/react/src/React.js index 61a59f83c530b..81247eba6f0d4 100644 --- a/packages/react/src/React.js +++ b/packages/react/src/React.js @@ -7,9 +7,9 @@ import assign from 'object-assign'; import ReactVersion from 'shared/ReactVersion'; -import {REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols'; +import {REACT_FRAGMENT_TYPE, REACT_STRICT_MODE_TYPE} from 'shared/ReactSymbols'; -import {Component, PureComponent, AsyncComponent} from './ReactBaseClasses'; +import {AsyncComponent, Component, PureComponent} from './ReactBaseClasses'; import {forEach, map, count, toArray, only} from './ReactChildren'; import ReactCurrentOwner from './ReactCurrentOwner'; import { @@ -39,6 +39,7 @@ const React = { unstable_AsyncComponent: AsyncComponent, Fragment: REACT_FRAGMENT_TYPE, + StrictMode: REACT_STRICT_MODE_TYPE, createElement: __DEV__ ? createElementWithValidation : createElement, cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement, diff --git a/packages/react/src/ReactBaseClasses.js b/packages/react/src/ReactBaseClasses.js index 13d4ab999bfba..03a2c7225bbc6 100644 --- a/packages/react/src/ReactBaseClasses.js +++ b/packages/react/src/ReactBaseClasses.js @@ -117,34 +117,32 @@ if (__DEV__) { } } +function ComponentDummy() {} +ComponentDummy.prototype = Component.prototype; + /** - * Base class helpers for the updating state of a component. + * Convenience component with default shallow equality check for sCU. */ function PureComponent(props, context, updater) { - // Duplicated from Component. this.props = props; this.context = context; this.refs = emptyObject; - // We initialize the default updater but the real one gets injected by the - // renderer. this.updater = updater || ReactNoopUpdateQueue; } -function ComponentDummy() {} -ComponentDummy.prototype = Component.prototype; const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy()); pureComponentPrototype.constructor = PureComponent; // Avoid an extra prototype jump for these methods. Object.assign(pureComponentPrototype, Component.prototype); pureComponentPrototype.isPureReactComponent = true; +/** + * Special component type that opts subtree into async rendering mode. + */ function AsyncComponent(props, context, updater) { - // Duplicated from Component. this.props = props; this.context = context; this.refs = emptyObject; - // We initialize the default updater but the real one gets injected by the - // renderer. this.updater = updater || ReactNoopUpdateQueue; } @@ -157,4 +155,4 @@ asyncComponentPrototype.render = function() { return this.props.children; }; -export {Component, PureComponent, AsyncComponent}; +export {AsyncComponent, Component, PureComponent}; diff --git a/packages/react/src/ReactElementValidator.js b/packages/react/src/ReactElementValidator.js index bdf67881a77fb..56588c4a253fc 100644 --- a/packages/react/src/ReactElementValidator.js +++ b/packages/react/src/ReactElementValidator.js @@ -15,7 +15,11 @@ import lowPriorityWarning from 'shared/lowPriorityWarning'; import describeComponentFrame from 'shared/describeComponentFrame'; import getComponentName from 'shared/getComponentName'; -import {getIteratorFn, REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols'; +import { + getIteratorFn, + REACT_FRAGMENT_TYPE, + REACT_STRICT_MODE_TYPE, +} from 'shared/ReactSymbols'; import checkPropTypes from 'prop-types/checkPropTypes'; import warning from 'fbjs/lib/warning'; @@ -282,7 +286,8 @@ export function createElementWithValidation(type, props, children) { typeof type === 'string' || typeof type === 'function' || // Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill. - type === REACT_FRAGMENT_TYPE; + type === REACT_FRAGMENT_TYPE || + type === REACT_STRICT_MODE_TYPE; // We warn in this case but don't throw. We expect the element creation to // succeed and there will likely be errors in render. diff --git a/packages/react/src/__tests__/ReactAsyncClassComponent-test.internal.js b/packages/react/src/__tests__/ReactStrictMode-test.internal.js similarity index 69% rename from packages/react/src/__tests__/ReactAsyncClassComponent-test.internal.js rename to packages/react/src/__tests__/ReactStrictMode-test.internal.js index 12b6d7a7a7c9b..15f339a66a770 100644 --- a/packages/react/src/__tests__/ReactAsyncClassComponent-test.internal.js +++ b/packages/react/src/__tests__/ReactStrictMode-test.internal.js @@ -13,7 +13,7 @@ let React; let ReactFeatureFlags; let ReactTestRenderer; -describe('ReactAsyncClassComponent', () => { +describe('ReactStrictMode', () => { describe('debugRenderPhaseSideEffects', () => { beforeEach(() => { jest.resetModules(); @@ -194,7 +194,7 @@ describe('ReactAsyncClassComponent', () => { expect(() => { rendered = ReactTestRenderer.create(); }).toWarnDev( - 'Unsafe lifecycle methods were found within the following async tree:' + + 'Unsafe lifecycle methods were found within a strict-mode tree:' + '\n in AsyncRoot (at **)' + '\n in SyncRoot (at **)' + '\n\ncomponentWillMount: Please update the following components ' + @@ -204,7 +204,7 @@ describe('ReactAsyncClassComponent', () => { '\n\ncomponentWillUpdate: Please update the following components ' + 'to use componentDidUpdate instead: AsyncRoot' + '\n\nLearn more about this warning here:' + - '\nhttps://fb.me/react-async-component-lifecycle-hooks', + '\nhttps://fb.me/react-strict-mode-warnings', ); // Dedupe @@ -248,7 +248,7 @@ describe('ReactAsyncClassComponent', () => { expect( () => (rendered = ReactTestRenderer.create()), ).toWarnDev( - 'Unsafe lifecycle methods were found within the following async tree:' + + 'Unsafe lifecycle methods were found within a strict-mode tree:' + '\n in AsyncRoot (at **)' + '\n in SyncRoot (at **)' + '\n\ncomponentWillMount: Please update the following components ' + @@ -258,7 +258,7 @@ describe('ReactAsyncClassComponent', () => { '\n\ncomponentWillUpdate: Please update the following components ' + 'to use componentDidUpdate instead: AsyncRoot, Parent' + '\n\nLearn more about this warning here:' + - '\nhttps://fb.me/react-async-component-lifecycle-hooks', + '\nhttps://fb.me/react-strict-mode-warnings', ); // Dedupe @@ -322,13 +322,13 @@ describe('ReactAsyncClassComponent', () => { expect( () => (rendered = ReactTestRenderer.create()), ).toWarnDev([ - 'Unsafe lifecycle methods were found within the following async tree:' + + 'Unsafe lifecycle methods were found within a strict-mode tree:' + '\n in AsyncRootOne (at **)' + '\n in div (at **)' + '\n in SyncRoot (at **)' + '\n\ncomponentWillMount: Please update the following components ' + 'to use componentDidMount instead: Bar, Foo', - 'Unsafe lifecycle methods were found within the following async tree:' + + 'Unsafe lifecycle methods were found within a strict-mode tree:' + '\n in AsyncRootTwo (at **)' + '\n in div (at **)' + '\n in SyncRoot (at **)' + @@ -364,21 +364,21 @@ describe('ReactAsyncClassComponent', () => { expect(() => { rendered = ReactTestRenderer.create(); }).toWarnDev( - 'Unsafe lifecycle methods were found within the following async tree:' + + 'Unsafe lifecycle methods were found within a strict-mode tree:' + '\n in AsyncRoot (at **)' + '\n\ncomponentWillMount: Please update the following components ' + 'to use componentDidMount instead: Foo' + '\n\nLearn more about this warning here:' + - '\nhttps://fb.me/react-async-component-lifecycle-hooks', + '\nhttps://fb.me/react-strict-mode-warnings', ); expect(() => rendered.update()).toWarnDev( - 'Unsafe lifecycle methods were found within the following async tree:' + + 'Unsafe lifecycle methods were found within a strict-mode tree:' + '\n in AsyncRoot (at **)' + '\n\ncomponentWillMount: Please update the following components ' + 'to use componentDidMount instead: Bar' + '\n\nLearn more about this warning here:' + - '\nhttps://fb.me/react-async-component-lifecycle-hooks', + '\nhttps://fb.me/react-strict-mode-warnings', ); // Dedupe @@ -422,15 +422,178 @@ describe('ReactAsyncClassComponent', () => { expect(() => { ReactTestRenderer.create(); }).toWarnDev( - 'Unsafe lifecycle methods were found within the following async tree:' + + 'Unsafe lifecycle methods were found within a strict-mode tree:' + '\n in AsyncRoot (at **)' + '\n\ncomponentWillMount: Please update the following components ' + 'to use componentDidMount instead: Bar' + '\n\nLearn more about this warning here:' + - '\nhttps://fb.me/react-async-component-lifecycle-hooks', + '\nhttps://fb.me/react-strict-mode-warnings', ); expect(caughtError).not.toBe(null); }); + + it('should also warn inside of "strict" mode trees', () => { + const {StrictMode} = React; + + class SyncRoot extends React.Component { + UNSAFE_componentWillMount() {} + UNSAFE_componentWillUpdate() {} + UNSAFE_componentWillReceiveProps() {} + render() { + return ( + + + + ); + } + } + function Wrapper({children}) { + return ( +
+ + +
+ ); + } + class Foo extends React.Component { + UNSAFE_componentWillReceiveProps() {} + render() { + return null; + } + } + class Bar extends React.Component { + UNSAFE_componentWillReceiveProps() {} + render() { + return null; + } + } + + expect(() => ReactTestRenderer.create()).toWarnDev( + 'Unsafe lifecycle methods were found within a strict-mode tree:' + + '\n in SyncRoot (at **)' + + '\n\ncomponentWillReceiveProps: Please update the following components ' + + 'to use static getDerivedStateFromProps instead: Bar, Foo' + + '\n\nLearn more about this warning here:' + + '\nhttps://fb.me/react-strict-mode-warnings', + ); + + // Dedupe + const rendered = ReactTestRenderer.create(); + rendered.update(); + }); + }); + + describe('symbol checks', () => { + beforeEach(() => { + jest.resetModules(); + React = require('react'); + ReactTestRenderer = require('react-test-renderer'); + }); + + it('should switch from StrictMode to a Fragment and reset state', () => { + const {Fragment, StrictMode} = React; + + function ParentComponent({useFragment}) { + return useFragment ? ( + + + + ) : ( + + + + ); + } + + class ChildComponent extends React.Component { + state = { + count: 0, + }; + static getDerivedStateFromProps(nextProps, prevState) { + return { + count: prevState.count + 1, + }; + } + render() { + return `count:${this.state.count}`; + } + } + + const rendered = ReactTestRenderer.create( + , + ); + expect(rendered.toJSON()).toBe('count:1'); + rendered.update(); + expect(rendered.toJSON()).toBe('count:1'); + }); + + it('should switch from a Fragment to StrictMode and reset state', () => { + const {Fragment, StrictMode} = React; + + function ParentComponent({useFragment}) { + return useFragment ? ( + + + + ) : ( + + + + ); + } + + class ChildComponent extends React.Component { + state = { + count: 0, + }; + static getDerivedStateFromProps(nextProps, prevState) { + return { + count: prevState.count + 1, + }; + } + render() { + return `count:${this.state.count}`; + } + } + + const rendered = ReactTestRenderer.create( + , + ); + expect(rendered.toJSON()).toBe('count:1'); + rendered.update(); + expect(rendered.toJSON()).toBe('count:1'); + }); + + it('should update with StrictMode without losing state', () => { + const {StrictMode} = React; + + function ParentComponent() { + return ( + + + + ); + } + + class ChildComponent extends React.Component { + state = { + count: 0, + }; + static getDerivedStateFromProps(nextProps, prevState) { + return { + count: prevState.count + 1, + }; + } + render() { + return `count:${this.state.count}`; + } + } + + const rendered = ReactTestRenderer.create(); + expect(rendered.toJSON()).toBe('count:1'); + rendered.update(); + expect(rendered.toJSON()).toBe('count:2'); + }); }); }); diff --git a/packages/shared/ReactSymbols.js b/packages/shared/ReactSymbols.js index d914c0f25e270..de205bd2d244e 100644 --- a/packages/shared/ReactSymbols.js +++ b/packages/shared/ReactSymbols.js @@ -24,6 +24,9 @@ export const REACT_PORTAL_TYPE = hasSymbol export const REACT_FRAGMENT_TYPE = hasSymbol ? Symbol.for('react.fragment') : 0xeacb; +export const REACT_STRICT_MODE_TYPE = hasSymbol + ? Symbol.for('react.strict_mode') + : 0xeacc; const MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator; const FAUX_ITERATOR_SYMBOL = '@@iterator'; diff --git a/packages/shared/ReactTypeOfWork.js b/packages/shared/ReactTypeOfWork.js index ca5da127c7c19..899323c6c4c77 100644 --- a/packages/shared/ReactTypeOfWork.js +++ b/packages/shared/ReactTypeOfWork.js @@ -7,7 +7,7 @@ * @flow */ -export type TypeOfWork = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; +export type TypeOfWork = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11; export const IndeterminateComponent = 0; // Before we know whether it is functional or class export const FunctionalComponent = 1; @@ -20,3 +20,4 @@ export const CallComponent = 7; export const CallHandlerPhase = 8; export const ReturnComponent = 9; export const Fragment = 10; +export const Mode = 11;