diff --git a/Libraries/Animated/src/AnimatedImplementation.js b/Libraries/Animated/src/AnimatedImplementation.js index 525fb6f23..550c8062d 100644 --- a/Libraries/Animated/src/AnimatedImplementation.js +++ b/Libraries/Animated/src/AnimatedImplementation.js @@ -77,11 +77,11 @@ class Animated { throw new Error('This node cannot be made a "native" animated node'); } } - __getNativeTag(): number { + __getNativeTag(): ?number { NativeAnimatedHelper.assertNativeAnimatedModule(); invariant(this.__isNative, 'Attempt to get native tag from node not marked as "native"'); if (this.__nativeTag == null) { - var nativeTag: number = NativeAnimatedHelper.generateNewNodeTag(); + var nativeTag: ?number = NativeAnimatedHelper.generateNewNodeTag(); NativeAnimatedAPI.createAnimatedNode(nativeTag, this.__getNativeConfig()); this.__nativeTag = nativeTag; } diff --git a/Libraries/Animated/src/NativeAnimatedHelper.js b/Libraries/Animated/src/NativeAnimatedHelper.js index 9949155ec..74c71c7dc 100644 --- a/Libraries/Animated/src/NativeAnimatedHelper.js +++ b/Libraries/Animated/src/NativeAnimatedHelper.js @@ -23,7 +23,7 @@ type EndResult = {finished: boolean}; type EndCallback = (result: EndResult) => void; type EventMapping = { nativeEventPath: Array, - animatedValueTag: number, + animatedValueTag: ?number, }; let nativeEventEmitter; @@ -33,67 +33,67 @@ let nativeEventEmitter; * the native module methods */ const API = { - createAnimatedNode: function(tag: number, config: Object): void { + createAnimatedNode: function(tag: ?number, config: Object): void { assertNativeAnimatedModule(); NativeAnimatedModule.createAnimatedNode(tag, config); }, - startListeningToAnimatedNodeValue: function(tag: number) { + startListeningToAnimatedNodeValue: function(tag: ?number) { assertNativeAnimatedModule(); NativeAnimatedModule.startListeningToAnimatedNodeValue(tag); }, - stopListeningToAnimatedNodeValue: function(tag: number) { + stopListeningToAnimatedNodeValue: function(tag: ?number) { assertNativeAnimatedModule(); NativeAnimatedModule.stopListeningToAnimatedNodeValue(tag); }, - connectAnimatedNodes: function(parentTag: number, childTag: number): void { + connectAnimatedNodes: function(parentTag: ?number, childTag: ?number): void { assertNativeAnimatedModule(); NativeAnimatedModule.connectAnimatedNodes(parentTag, childTag); }, - disconnectAnimatedNodes: function(parentTag: number, childTag: number): void { + disconnectAnimatedNodes: function(parentTag: ?number, childTag: ?number): void { assertNativeAnimatedModule(); NativeAnimatedModule.disconnectAnimatedNodes(parentTag, childTag); }, - startAnimatingNode: function(animationId: number, nodeTag: number, config: Object, endCallback: EndCallback): void { + startAnimatingNode: function(animationId: ?number, nodeTag: ?number, config: Object, endCallback: EndCallback): void { assertNativeAnimatedModule(); NativeAnimatedModule.startAnimatingNode(animationId, nodeTag, config, endCallback); }, - stopAnimation: function(animationId: number) { + stopAnimation: function(animationId: ?number) { assertNativeAnimatedModule(); NativeAnimatedModule.stopAnimation(animationId); }, - setAnimatedNodeValue: function(nodeTag: number, value: number): void { + setAnimatedNodeValue: function(nodeTag: ?number, value: ?number): void { assertNativeAnimatedModule(); NativeAnimatedModule.setAnimatedNodeValue(nodeTag, value); }, - setAnimatedNodeOffset: function(nodeTag: number, offset: number): void { + setAnimatedNodeOffset: function(nodeTag: ?number, offset: ?number): void { assertNativeAnimatedModule(); NativeAnimatedModule.setAnimatedNodeOffset(nodeTag, offset); }, - flattenAnimatedNodeOffset: function(nodeTag: number): void { + flattenAnimatedNodeOffset: function(nodeTag: ?number): void { assertNativeAnimatedModule(); NativeAnimatedModule.flattenAnimatedNodeOffset(nodeTag); }, - extractAnimatedNodeOffset: function(nodeTag: number): void { + extractAnimatedNodeOffset: function(nodeTag: ?number): void { assertNativeAnimatedModule(); NativeAnimatedModule.extractAnimatedNodeOffset(nodeTag); }, - connectAnimatedNodeToView: function(nodeTag: number, viewTag: number): void { + connectAnimatedNodeToView: function(nodeTag: ?number, viewTag: ?number): void { assertNativeAnimatedModule(); NativeAnimatedModule.connectAnimatedNodeToView(nodeTag, viewTag); }, - disconnectAnimatedNodeFromView: function(nodeTag: number, viewTag: number): void { + disconnectAnimatedNodeFromView: function(nodeTag: ?number, viewTag: ?number): void { assertNativeAnimatedModule(); NativeAnimatedModule.disconnectAnimatedNodeFromView(nodeTag, viewTag); }, - dropAnimatedNode: function(tag: number): void { + dropAnimatedNode: function(tag: ?number): void { assertNativeAnimatedModule(); NativeAnimatedModule.dropAnimatedNode(tag); }, - addAnimatedEventToView: function(viewTag: number, eventName: string, eventMapping: EventMapping) { + addAnimatedEventToView: function(viewTag: ?number, eventName: string, eventMapping: EventMapping) { assertNativeAnimatedModule(); NativeAnimatedModule.addAnimatedEventToView(viewTag, eventName, eventMapping); }, - removeAnimatedEventFromView(viewTag: number, eventName: string) { + removeAnimatedEventFromView(viewTag: ?number, eventName: string) { assertNativeAnimatedModule(); NativeAnimatedModule.removeAnimatedEventFromView(viewTag, eventName); } diff --git a/Libraries/Renderer/src/ReactVersion.js b/Libraries/Renderer/src/ReactVersion.js index 6d0c2ba7e..594ac0e3a 100644 --- a/Libraries/Renderer/src/ReactVersion.js +++ b/Libraries/Renderer/src/ReactVersion.js @@ -11,4 +11,4 @@ 'use strict'; -module.exports = '16.0.0-alpha.2'; +module.exports = '16.0.0-alpha.3'; diff --git a/Libraries/Renderer/src/renderers/native/ReactNativeComponentTree.js b/Libraries/Renderer/src/renderers/native/ReactNativeComponentTree.js index 62c0e6370..e09166778 100644 --- a/Libraries/Renderer/src/renderers/native/ReactNativeComponentTree.js +++ b/Libraries/Renderer/src/renderers/native/ReactNativeComponentTree.js @@ -53,6 +53,7 @@ function uncacheNode(inst) { function uncacheFiberNode(tag) { delete instanceCache[tag]; + delete instanceProps[tag]; } function getInstanceFromTag(tag) { @@ -61,7 +62,9 @@ function getInstanceFromTag(tag) { function getTagFromInstance(inst) { // TODO (bvaughn) Clean up once Stack is deprecated - var tag = inst._rootNodeID || inst.stateNode._nativeTag; + var tag = typeof inst.tag !== 'number' + ? inst._rootNodeID + : inst.stateNode._nativeTag; invariant(tag, 'All native instances should have a tag.'); return tag; } diff --git a/Libraries/Renderer/src/renderers/native/ReactNativeFiber.js b/Libraries/Renderer/src/renderers/native/ReactNativeFiber.js index 181eb7075..1d73e80b8 100644 --- a/Libraries/Renderer/src/renderers/native/ReactNativeFiber.js +++ b/Libraries/Renderer/src/renderers/native/ReactNativeFiber.js @@ -96,11 +96,7 @@ const NativeRenderer = ReactFiberReconciler({ }, appendInitialChild(parentInstance : Instance, child : Instance | TextInstance) : void { - if (typeof child === 'number') { - parentInstance._children.push(child); - } else { - parentInstance._children.push(child); - } + parentInstance._children.push(child); }, commitTextUpdate( diff --git a/Libraries/Renderer/src/renderers/native/ReactNativeGlobalResponderHandler.js b/Libraries/Renderer/src/renderers/native/ReactNativeGlobalResponderHandler.js index 4efacfd82..73bfe0472 100644 --- a/Libraries/Renderer/src/renderers/native/ReactNativeGlobalResponderHandler.js +++ b/Libraries/Renderer/src/renderers/native/ReactNativeGlobalResponderHandler.js @@ -16,10 +16,10 @@ var ReactNativeGlobalResponderHandler = { onChange: function(from, to, blockNativeResponder) { if (to !== null) { // TODO (bvaughn) Clean up once Stack is deprecated - UIManager.setJSResponder( - to._rootNodeID || to.stateNode._nativeTag, - blockNativeResponder - ); + var tag = typeof to.tag !== 'number' + ? to._rootNodeID + : to.stateNode._nativeTag; + UIManager.setJSResponder(tag, blockNativeResponder); } else { UIManager.clearJSResponder(); } diff --git a/Libraries/Renderer/src/renderers/native/__tests__/ReactNativeEvents-test.js b/Libraries/Renderer/src/renderers/native/__tests__/ReactNativeEvents-test.js index 5c320d942..5204b5ab8 100644 --- a/Libraries/Renderer/src/renderers/native/__tests__/ReactNativeEvents-test.js +++ b/Libraries/Renderer/src/renderers/native/__tests__/ReactNativeEvents-test.js @@ -15,6 +15,7 @@ var RCTEventEmitter; var React; var ReactErrorUtils; var ReactNative; +var ResponderEventPlugin; var UIManager; var createReactNativeComponentClass; @@ -25,6 +26,7 @@ beforeEach(() => { React = require('React'); ReactErrorUtils = require('ReactErrorUtils'); ReactNative = require('ReactNative'); + ResponderEventPlugin = require('ResponderEventPlugin'); UIManager = require('UIManager'); createReactNativeComponentClass = require('createReactNativeComponentClass'); @@ -91,3 +93,97 @@ it('handles events', () => { 'outer touchend', ]); }); + +it('handles when a responder is unmounted while a touch sequence is in progress', () => { + var EventEmitter = RCTEventEmitter.register.mock.calls[0][0]; + var View = createReactNativeComponentClass({ + validAttributes: { id: true }, + uiViewClassName: 'View', + }); + + function getViewById(id) { + return UIManager.createView.mock.calls.find( + args => args[3] && args[3].id === id + )[0]; + } + + function getResponderId() { + const responder = ResponderEventPlugin._getResponder(); + if (responder === null) { + return null; + } + const props = typeof responder.tag === 'number' + ? responder.memoizedProps + : responder._currentElement.props; + return props ? props.id : null; + } + + var log = []; + ReactNative.render( + + + log.push('one responder end')} + onResponderStart={() => log.push('one responder start')} + onStartShouldSetResponder={() => true} + /> + + + log.push('two responder end')} + onResponderStart={() => log.push('two responder start')} + onStartShouldSetResponder={() => true} + /> + + , + 1 + ); + + EventEmitter.receiveTouches( + 'topTouchStart', + [{target: getViewById('one'), identifier: 17}], + [0] + ); + + expect(getResponderId()).toBe('one'); + expect(log).toEqual(['one responder start']); + log.splice(0); + + ReactNative.render( + + + log.push('two responder end')} + onResponderStart={() => log.push('two responder start')} + onStartShouldSetResponder={() => true} + /> + + , + 1 + ); + + // TODO Verify the onResponderEnd listener has been called (before the unmount) + // expect(log).toEqual(['one responder end']); + // log.splice(0); + + EventEmitter.receiveTouches( + 'topTouchEnd', + [{target: getViewById('two'), identifier: 17}], + [0] + ); + + expect(getResponderId()).toBeNull(); + expect(log).toEqual([]); + + EventEmitter.receiveTouches( + 'topTouchStart', + [{target: getViewById('two'), identifier: 17}], + [0] + ); + + expect(getResponderId()).toBe('two'); + expect(log).toEqual(['two responder start']); +}); diff --git a/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass.js b/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass.js index 136e2787f..bf53cf816 100644 --- a/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass.js +++ b/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass.js @@ -27,14 +27,14 @@ type ReactNativeBaseComponentViewConfig = { * @param {string} config iOS View configuration. * @private */ -const createReactNativeFiberComponentClass = function( - viewConfig: ReactNativeBaseComponentViewConfig -): ReactClass { - // TODO(sema): This actually returns a string. Need to fix this before - // we deploy Fiber. - return (ReactNativeViewConfigRegistry.register(viewConfig) : any); -}; - + const createReactNativeFiberComponentClass = function( + viewConfig: ReactNativeBaseComponentViewConfig + ): ReactClass { + // TODO(sema): This actually returns a string. Need to fix this before + // we deploy Fiber. + return (ReactNativeViewConfigRegistry.register(viewConfig) : any); + }; + /** * @param {string} config iOS View configuration. * @private @@ -59,6 +59,6 @@ const createReactNativeComponentClass = function( return ((Constructor: any): ReactClass); }; -module.exports = (ReactNativeFeatureFlags.useFiber +module.exports = ReactNativeFeatureFlags.useFiber ? createReactNativeFiberComponentClass - : createReactNativeComponentClass); + : createReactNativeComponentClass; diff --git a/Libraries/Renderer/src/renderers/native/findNodeHandle.js b/Libraries/Renderer/src/renderers/native/findNodeHandle.js index 948299a2f..7edb37892 100644 --- a/Libraries/Renderer/src/renderers/native/findNodeHandle.js +++ b/Libraries/Renderer/src/renderers/native/findNodeHandle.js @@ -53,8 +53,7 @@ import type { ReactInstance } from 'ReactInstanceType'; let injectedFindNode; let injectedFindRootNodeID; -// TODO(sema): Type this more specifically. -function findNodeHandle(componentOrHandle: any): any { +function findNodeHandle(componentOrHandle: any): ?number { if (__DEV__) { // TODO: fix this unsafe cast to work with Fiber. var owner = ((ReactCurrentOwner.current: any): ReactInstance | null); diff --git a/Libraries/Renderer/src/renderers/shared/fiber/ReactFiber.js b/Libraries/Renderer/src/renderers/shared/fiber/ReactFiber.js index 347c95965..bd89a93f8 100644 --- a/Libraries/Renderer/src/renderers/shared/fiber/ReactFiber.js +++ b/Libraries/Renderer/src/renderers/shared/fiber/ReactFiber.js @@ -223,8 +223,12 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber { fiber._debugID = debugCounter++; fiber._debugSource = null; fiber._debugOwner = null; + if (typeof Object.preventExtensions === 'function') { + Object.preventExtensions(fiber); + } } + return fiber; }; diff --git a/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberBeginWork.js b/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberBeginWork.js index a38eff7f1..b9db783ae 100644 --- a/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -67,7 +67,6 @@ var invariant = require('fbjs/lib/invariant'); if (__DEV__) { var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); var warning = require('fbjs/lib/warning'); - var warnedAboutStatelessRefs = {}; } @@ -479,6 +478,15 @@ module.exports = function( // Proceed under the assumption that this is a functional component workInProgress.tag = FunctionalComponent; if (__DEV__) { + const Component = workInProgress.type; + + if (Component) { + warning( + !Component.childContextTypes, + '%s(...): childContextTypes cannot be defined on a functional component.', + Component.displayName || Component.name || 'Component' + ); + } if (workInProgress.ref !== null) { let info = ''; const ownerName = ReactDebugCurrentFiber.getCurrentFiberOwnerName(); diff --git a/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberClassComponent.js b/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberClassComponent.js index bccd354c6..bdb377b54 100644 --- a/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberClassComponent.js +++ b/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberClassComponent.js @@ -291,6 +291,7 @@ module.exports = function( instance.props = props; instance.state = state; + instance.refs = emptyObject; instance.context = getMaskedContext(workInProgress, unmaskedContext); if (typeof instance.componentWillMount === 'function') { @@ -408,6 +409,19 @@ module.exports = function( if (oldProps !== newProps || oldContext !== newContext) { if (typeof instance.componentWillReceiveProps === 'function') { instance.componentWillReceiveProps(newProps, newContext); + + if (instance.state !== workInProgress.memoizedState) { + if (__DEV__) { + warning( + false, + '%s.componentWillReceiveProps(): Assigning directly to ' + + 'this.state is deprecated (except inside a component\'s ' + + 'constructor). Use setState instead.', + getComponentName(workInProgress) + ); + } + updater.enqueueReplaceState(instance, instance.state, null); + } } } diff --git a/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberContext.js b/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberContext.js index 0cbb56a84..a40dd9971 100644 --- a/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberContext.js +++ b/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberContext.js @@ -34,6 +34,7 @@ const { if (__DEV__) { var checkReactTypeSpec = require('checkReactTypeSpec'); + var ReactDebugCurrentFrame = require('react/lib/ReactDebugCurrentFrame'); var warnedAboutMissingGetChildContext = {}; } @@ -91,7 +92,9 @@ exports.getMaskedContext = function(workInProgress : Fiber, unmaskedContext : Ob if (__DEV__) { const name = getComponentName(workInProgress); - checkReactTypeSpec(contextTypes, context, 'context', name, null, workInProgress); + ReactDebugCurrentFrame.current = workInProgress; + checkReactTypeSpec(contextTypes, context, 'context', name); + ReactDebugCurrentFrame.current = null; } // Cache unmasked context so we can avoid recreating masked context unless necessary. @@ -182,7 +185,9 @@ function processChildContext(fiber : Fiber, parentContext : Object, isReconcilin // assume anything about the given fiber. We won't pass it down if we aren't sure. // TODO: remove this hack when we delete unstable_renderSubtree in Fiber. const workInProgress = isReconciling ? fiber : null; - checkReactTypeSpec(childContextTypes, childContext, 'childContext', name, null, workInProgress); + ReactDebugCurrentFrame.current = workInProgress; + checkReactTypeSpec(childContextTypes, childContext, 'child context', name); + ReactDebugCurrentFrame.current = null; } return {...parentContext, ...childContext}; } diff --git a/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberDevToolsHook.js b/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberDevToolsHook.js index bff086f41..6fc566684 100644 --- a/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberDevToolsHook.js +++ b/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberDevToolsHook.js @@ -12,15 +12,13 @@ 'use strict'; +var warning = require('fbjs/lib/warning'); + import type { Fiber } from 'ReactFiber'; import type { FiberRoot } from 'ReactFiberRoot'; declare var __REACT_DEVTOOLS_GLOBAL_HOOK__ : Object | void; -if (__DEV__) { - var warning = require('fbjs/lib/warning'); -} - let rendererID = null; let injectInternals = null; let onCommitRoot = null; diff --git a/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberScheduler.js b/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberScheduler.js index f9d1a915f..0da191418 100644 --- a/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/Libraries/Renderer/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -21,6 +21,7 @@ export type CapturedError = { componentName : ?string, componentStack : string, error : Error, + errorBoundary : ?Object, errorBoundaryFound : boolean, errorBoundaryName : string | null, willRetry : boolean, @@ -86,8 +87,21 @@ var { var invariant = require('fbjs/lib/invariant'); if (__DEV__) { + var warning = require('fbjs/lib/warning'); var ReactFiberInstrumentation = require('ReactFiberInstrumentation'); var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); + + var warnAboutUpdateOnUnmounted = function(instance : ReactClass) { + const ctor = instance.constructor; + warning( + false, + 'Can only update a mounted or mounting component. This usually means ' + + 'you called setState, replaceState, or forceUpdate on an unmounted ' + + 'component. This is a no-op.\n\nPlease check the code for the ' + + '%s component.', + ctor && (ctor.displayName || ctor.name) || 'ReactClass' + ); + }; } var timeHeuristicForUnitOfWork = 1; @@ -349,6 +363,9 @@ module.exports = function(config : HostConfig(config : HostConfig(config : HostConfig) : boolean { + if (__DEV__) { + const owner = (ReactCurrentOwner.current : any); + if (owner !== null && owner.tag === ClassComponent) { + const ownerFiber : Fiber = owner; + const instance = ownerFiber.stateNode; + warning( + instance._warnedAboutRefsInRender, + '%s is accessing isMounted inside its render() function. ' + + 'render() should be a pure function of props and state. It should ' + + 'never access something that requires stale data from the previous ' + + 'render, such as refs. Move this logic to componentDidMount and ' + + 'componentDidUpdate instead.', + getComponentName(ownerFiber) + ); + instance._warnedAboutRefsInRender = true; + } + } + var fiber : ?Fiber = ReactInstanceMap.get(component); if (!fiber) { return false; @@ -243,7 +267,7 @@ exports.findCurrentHostFiber = function(parent : Fiber) : Fiber | null { return null; }; -exports.getComponentName = function(fiber: Fiber): string { +function getComponentName(fiber: Fiber): string { const type = fiber.type; const instance = fiber.stateNode; const constructor = instance && instance.constructor; @@ -252,4 +276,5 @@ exports.getComponentName = function(fiber: Fiber): string { type.name || (constructor && constructor.name) || 'A Component' ); -}; +} +exports.getComponentName = getComponentName; diff --git a/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js b/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js index 728624232..318084402 100644 --- a/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js +++ b/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js @@ -24,6 +24,7 @@ var ReactReconciler = require('ReactReconciler'); if (__DEV__) { var checkReactTypeSpec = require('checkReactTypeSpec'); + var ReactDebugCurrentFrame = require('react/lib/ReactDebugCurrentFrame'); var warningAboutMissingGetChildContext = {}; } @@ -33,8 +34,6 @@ var shallowEqual = require('fbjs/lib/shallowEqual'); var shouldUpdateReactComponent = require('shouldUpdateReactComponent'); var warning = require('fbjs/lib/warning'); -import type { ReactPropTypeLocations } from 'ReactPropTypeLocations'; - function StatelessComponent(Component) { } StatelessComponent.prototype.render = function() { @@ -687,7 +686,7 @@ var ReactCompositeComponent = { this._checkContextTypes( Component.childContextTypes, childContext, - 'childContext' + 'child context' ); } for (var name in childContext) { @@ -730,17 +729,17 @@ var ReactCompositeComponent = { _checkContextTypes: function( typeSpecs, values, - location: ReactPropTypeLocations, + location: string, ) { if (__DEV__) { + ReactDebugCurrentFrame.current = this._debugID; checkReactTypeSpec( typeSpecs, values, location, - this.getName(), - null, - this._debugID + this.getName() ); + ReactDebugCurrentFrame.current = null; } }, @@ -850,6 +849,7 @@ var ReactCompositeComponent = { // _pendingStateQueue which will ensure that any state updates gets // immediately reconciled instead of waiting for the next batch. if (willReceive && inst.componentWillReceiveProps) { + const beforeState = inst.state; if (__DEV__) { measureLifeCyclePerf( () => inst.componentWillReceiveProps(nextProps, nextContext), @@ -859,6 +859,20 @@ var ReactCompositeComponent = { } else { inst.componentWillReceiveProps(nextProps, nextContext); } + const afterState = inst.state; + if (beforeState !== afterState) { + inst.state = beforeState; + inst.updater.enqueueReplaceState(inst, afterState); + if (__DEV__) { + warning( + false, + '%s.componentWillReceiveProps(): Assigning directly to ' + + 'this.state is deprecated (except inside a component\'s ' + + 'constructor). Use setState instead.', + this.getName() || 'ReactCompositeComponent' + ); + } + } } // If updating happens to enqueue any new updates, we shouldn't execute new diff --git a/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactUpdateQueue.js b/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactUpdateQueue.js index cda2470b5..0058f06df 100644 --- a/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactUpdateQueue.js +++ b/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactUpdateQueue.js @@ -38,16 +38,12 @@ function getInternalInstanceReadyForUpdate(publicInstance, callerName) { if (!internalInstance) { if (__DEV__) { var ctor = publicInstance.constructor; - // Only warn when we have a callerName. Otherwise we should be silent. - // We're probably calling from enqueueCallback. We don't want to warn - // there because we already warned for the corresponding lifecycle method. warning( - !callerName, - '%s(...): Can only update a mounted or mounting component. ' + - 'This usually means you called %s() on an unmounted component. ' + - 'This is a no-op.\n\nPlease check the code for the %s component.', - callerName, - callerName, + false, + 'Can only update a mounted or mounting component. This usually means ' + + 'you called setState, replaceState, or forceUpdate on an unmounted ' + + 'component. This is a no-op.\n\nPlease check the code for the ' + + '%s component.', ctor && (ctor.displayName || ctor.name) || 'ReactClass' ); } @@ -57,12 +53,10 @@ function getInternalInstanceReadyForUpdate(publicInstance, callerName) { if (__DEV__) { warning( ReactCurrentOwner.current == null, - '%s(...): Cannot update during an existing state transition (such as ' + - 'within `render` or another component\'s constructor). Render methods ' + - 'should be a pure function of props and state; constructor ' + - 'side-effects are an anti-pattern, but can be moved to ' + - '`componentWillMount`.', - callerName + 'Cannot update during an existing state transition (such as within ' + + '`render` or another component\'s constructor). Render methods should ' + + 'be a pure function of props and state; constructor side-effects are ' + + 'an anti-pattern, but can be moved to `componentWillMount`.', ); } @@ -134,10 +128,7 @@ var ReactUpdateQueue = { * @internal */ enqueueForceUpdate: function(publicInstance, callback, callerName) { - var internalInstance = getInternalInstanceReadyForUpdate( - publicInstance, - 'forceUpdate' - ); + var internalInstance = getInternalInstanceReadyForUpdate(publicInstance); if (!internalInstance) { return; @@ -174,10 +165,7 @@ var ReactUpdateQueue = { * @internal */ enqueueReplaceState: function(publicInstance, completeState, callback, callerName) { - var internalInstance = getInternalInstanceReadyForUpdate( - publicInstance, - 'replaceState' - ); + var internalInstance = getInternalInstanceReadyForUpdate(publicInstance); if (!internalInstance) { return; @@ -223,10 +211,7 @@ var ReactUpdateQueue = { ); } - var internalInstance = getInternalInstanceReadyForUpdate( - publicInstance, - 'setState' - ); + var internalInstance = getInternalInstanceReadyForUpdate(publicInstance); if (!internalInstance) { return; diff --git a/Libraries/Renderer/src/shared/types/ReactPropTypeLocationNames.js b/Libraries/Renderer/src/shared/types/ReactPropTypeLocationNames.js deleted file mode 100644 index 1b36b04cc..000000000 --- a/Libraries/Renderer/src/shared/types/ReactPropTypeLocationNames.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - * @providesModule ReactPropTypeLocationNames - */ - -'use strict'; - -import type { ReactPropTypeLocations } from 'ReactPropTypeLocations'; - -type NamesType = {[key: ReactPropTypeLocations]: string}; - -var ReactPropTypeLocationNames: NamesType = {}; - -if (__DEV__) { - ReactPropTypeLocationNames = { - prop: 'prop', - context: 'context', - childContext: 'child context', - }; -} - -module.exports = ReactPropTypeLocationNames; diff --git a/Libraries/Renderer/src/shared/types/ReactPropTypeLocations.js b/Libraries/Renderer/src/shared/types/ReactPropTypeLocations.js deleted file mode 100644 index a2cb905e1..000000000 --- a/Libraries/Renderer/src/shared/types/ReactPropTypeLocations.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - * @providesModule ReactPropTypeLocations - */ - -'use strict'; - -export type ReactPropTypeLocations = - 'prop' | - 'context' | - 'childContext'; diff --git a/Libraries/Renderer/src/shared/types/checkReactTypeSpec.js b/Libraries/Renderer/src/shared/types/checkReactTypeSpec.js index 27bf5003a..b115ee3f7 100644 --- a/Libraries/Renderer/src/shared/types/checkReactTypeSpec.js +++ b/Libraries/Renderer/src/shared/types/checkReactTypeSpec.js @@ -11,124 +11,22 @@ 'use strict'; -var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames'); -var ReactPropTypesSecret = require('ReactPropTypesSecret'); +var checkPropTypes = require('react/lib/checkPropTypes'); +var { getStackAddendum } = require('react/lib/ReactDebugCurrentFrame'); -var invariant = require('fbjs/lib/invariant'); -var warning = require('fbjs/lib/warning'); - -import type { ReactPropTypeLocations } from 'ReactPropTypeLocations'; - -var ReactComponentTreeHook; - -if ( - typeof process !== 'undefined' && - process.env && - process.env.NODE_ENV === 'test' -) { - // Temporary hack. - // Inline requires don't work well with Jest: - // https://github.com/facebook/react/issues/7240 - // Remove the inline requires when we don't need them anymore: - // https://github.com/facebook/react/pull/7178 - ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); -} - -var loggedTypeFailures = {}; - -/** - * Assert that the values match with the type specs. - * Error messages are memorized and will only be shown once. - * - * @param {object} typeSpecs Map of name to a ReactPropType - * @param {object} values Runtime values that need to be type-checked - * @param {string} location e.g. "prop", "context", "child context" - * @param {string} componentName Name of the component for error messages. - * @param {?object} element The React element that is being type-checked - * @param {?number} workInProgressOrDebugID The React component instance that is being type-checked - * @private - */ function checkReactTypeSpec( typeSpecs, values, - location: ReactPropTypeLocations, - componentName, - element, - // It is only safe to pass fiber if it is the work-in-progress version, and - // only during reconciliation (begin and complete phase). - workInProgressOrDebugID, + location: string, + componentName ) { - for (var typeSpecName in typeSpecs) { - if (typeSpecs.hasOwnProperty(typeSpecName)) { - var error; - // Prop type validation may throw. In case they do, we don't want to - // fail the render phase where it didn't fail before. So we log it. - // After these have been cleaned up, we'll let them throw. - try { - // This is intentionally an invariant that gets caught. It's the same - // behavior as without this statement except with a better message. - invariant( - typeof typeSpecs[typeSpecName] === 'function', - '%s: %s type `%s` is invalid; it must be a function, usually from ' + - 'React.PropTypes.', - componentName || 'React class', - ReactPropTypeLocationNames[location], - typeSpecName - ); - error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, ReactPropTypesSecret); - } catch (ex) { - error = ex; - } - warning( - !error || error instanceof Error, - '%s: type specification of %s `%s` is invalid; the type checker ' + - 'function must return `null` or an `Error` but returned a %s. ' + - 'You may have forgotten to pass an argument to the type checker ' + - 'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' + - 'shape all require an argument).', - componentName || 'React class', - ReactPropTypeLocationNames[location], - typeSpecName, - typeof error - ); - if (error instanceof Error && !(error.message in loggedTypeFailures)) { - // Only monitor this failure once because there tends to be a lot of the - // same error. - loggedTypeFailures[error.message] = true; - - var componentStackInfo = ''; - - if (__DEV__) { - if (!ReactComponentTreeHook) { - ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); - } - if (workInProgressOrDebugID != null) { - if (typeof workInProgressOrDebugID === 'number') { - // DebugID from Stack. - const debugID = workInProgressOrDebugID; - componentStackInfo = ReactComponentTreeHook.getStackAddendumByID(debugID); - } else if (typeof workInProgressOrDebugID.tag === 'number') { - // This is a Fiber. - // The stack will only be correct if this is a work in progress - // version and we're calling it during reconciliation. - const workInProgress = workInProgressOrDebugID; - componentStackInfo = ReactComponentTreeHook.getStackAddendumByWorkInProgressFiber(workInProgress); - } - } else if (element !== null) { - componentStackInfo = ReactComponentTreeHook.getCurrentStackAddendum(element); - } - } - - warning( - false, - 'Failed %s type: %s%s', - location, - error.message, - componentStackInfo - ); - } - } - } + checkPropTypes( + typeSpecs, + values, + location, + componentName, + getStackAddendum + ); } module.exports = checkReactTypeSpec; diff --git a/Libraries/StyleSheet/ColorPropType.js b/Libraries/StyleSheet/ColorPropType.js index 76f3a465d..faca88431 100644 --- a/Libraries/StyleSheet/ColorPropType.js +++ b/Libraries/StyleSheet/ColorPropType.js @@ -10,17 +10,14 @@ */ 'use strict'; -var ReactPropTypeLocationNames = require('react/lib/ReactPropTypeLocationNames'); - var normalizeColor = require('normalizeColor'); var colorPropType = function(isRequired, props, propName, componentName, location, propFullName) { var color = props[propName]; if (color === undefined || color === null) { if (isRequired) { - var locationName = ReactPropTypeLocationNames[location]; return new Error( - 'Required ' + locationName + ' `' + (propFullName || propName) + + 'Required ' + location + ' `' + (propFullName || propName) + '` was not specified in `' + componentName + '`.' ); } @@ -35,9 +32,8 @@ var colorPropType = function(isRequired, props, propName, componentName, locatio } if (normalizeColor(color) === null) { - var locationName = ReactPropTypeLocationNames[location]; return new Error( - 'Invalid ' + locationName + ' `' + (propFullName || propName) + + 'Invalid ' + location + ' `' + (propFullName || propName) + '` supplied to `' + componentName + '`: ' + color + '\n' + `Valid color formats are - '#f0f' (#rgb) diff --git a/package.json b/package.json index 5e9f378d2..dbb909b2b 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,7 @@ "react-native": "local-cli/wrong-react-native.js" }, "peerDependencies": { - "react": "16.0.0-alpha.2" + "react": "~16.0.0-alpha.3" }, "dependencies": { "absolute-path": "^0.0.0", @@ -159,7 +159,7 @@ "debug": "^2.2.0", "denodeify": "^1.2.1", "event-target-shim": "^1.0.5", - "fbjs": "^0.8.9", + "fbjs": "~0.8.9", "fbjs-scripts": "^0.7.0", "form-data": "^2.1.1", "fs-extra": "^0.26.2", @@ -222,9 +222,9 @@ "jest-repl": "19.0.2", "jest-runtime": "19.0.2", "mock-fs": "^3.11.0", - "react": "16.0.0-alpha.2", - "react-dom": "16.0.0-alpha.2", - "react-test-renderer": "16.0.0-alpha.2", + "react": "~16.0.0-alpha.3", + "react-dom": "~16.0.0-alpha.3", + "react-test-renderer": "~16.0.0-alpha.3", "shelljs": "0.6.0", "sinon": "^2.0.0-pre.2" }