Sync React Native with React 16.0.0-alpha.3 (take 2)

Reviewed By: spicyj

Differential Revision: D4623242

fbshipit-source-id: 7ddb057cb47e005dda73070f45d108af40d93c8f
This commit is contained in:
Andrew Clark 2017-02-27 17:19:00 -08:00 committed by Facebook Github Bot
parent 6439f65fcb
commit 41f1bcc5ac
23 changed files with 273 additions and 257 deletions

View File

@ -77,11 +77,11 @@ class Animated {
throw new Error('This node cannot be made a "native" animated node'); throw new Error('This node cannot be made a "native" animated node');
} }
} }
__getNativeTag(): number { __getNativeTag(): ?number {
NativeAnimatedHelper.assertNativeAnimatedModule(); NativeAnimatedHelper.assertNativeAnimatedModule();
invariant(this.__isNative, 'Attempt to get native tag from node not marked as "native"'); invariant(this.__isNative, 'Attempt to get native tag from node not marked as "native"');
if (this.__nativeTag == null) { if (this.__nativeTag == null) {
var nativeTag: number = NativeAnimatedHelper.generateNewNodeTag(); var nativeTag: ?number = NativeAnimatedHelper.generateNewNodeTag();
NativeAnimatedAPI.createAnimatedNode(nativeTag, this.__getNativeConfig()); NativeAnimatedAPI.createAnimatedNode(nativeTag, this.__getNativeConfig());
this.__nativeTag = nativeTag; this.__nativeTag = nativeTag;
} }

View File

@ -23,7 +23,7 @@ type EndResult = {finished: boolean};
type EndCallback = (result: EndResult) => void; type EndCallback = (result: EndResult) => void;
type EventMapping = { type EventMapping = {
nativeEventPath: Array<string>, nativeEventPath: Array<string>,
animatedValueTag: number, animatedValueTag: ?number,
}; };
let nativeEventEmitter; let nativeEventEmitter;
@ -33,67 +33,67 @@ let nativeEventEmitter;
* the native module methods * the native module methods
*/ */
const API = { const API = {
createAnimatedNode: function(tag: number, config: Object): void { createAnimatedNode: function(tag: ?number, config: Object): void {
assertNativeAnimatedModule(); assertNativeAnimatedModule();
NativeAnimatedModule.createAnimatedNode(tag, config); NativeAnimatedModule.createAnimatedNode(tag, config);
}, },
startListeningToAnimatedNodeValue: function(tag: number) { startListeningToAnimatedNodeValue: function(tag: ?number) {
assertNativeAnimatedModule(); assertNativeAnimatedModule();
NativeAnimatedModule.startListeningToAnimatedNodeValue(tag); NativeAnimatedModule.startListeningToAnimatedNodeValue(tag);
}, },
stopListeningToAnimatedNodeValue: function(tag: number) { stopListeningToAnimatedNodeValue: function(tag: ?number) {
assertNativeAnimatedModule(); assertNativeAnimatedModule();
NativeAnimatedModule.stopListeningToAnimatedNodeValue(tag); NativeAnimatedModule.stopListeningToAnimatedNodeValue(tag);
}, },
connectAnimatedNodes: function(parentTag: number, childTag: number): void { connectAnimatedNodes: function(parentTag: ?number, childTag: ?number): void {
assertNativeAnimatedModule(); assertNativeAnimatedModule();
NativeAnimatedModule.connectAnimatedNodes(parentTag, childTag); NativeAnimatedModule.connectAnimatedNodes(parentTag, childTag);
}, },
disconnectAnimatedNodes: function(parentTag: number, childTag: number): void { disconnectAnimatedNodes: function(parentTag: ?number, childTag: ?number): void {
assertNativeAnimatedModule(); assertNativeAnimatedModule();
NativeAnimatedModule.disconnectAnimatedNodes(parentTag, childTag); 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(); assertNativeAnimatedModule();
NativeAnimatedModule.startAnimatingNode(animationId, nodeTag, config, endCallback); NativeAnimatedModule.startAnimatingNode(animationId, nodeTag, config, endCallback);
}, },
stopAnimation: function(animationId: number) { stopAnimation: function(animationId: ?number) {
assertNativeAnimatedModule(); assertNativeAnimatedModule();
NativeAnimatedModule.stopAnimation(animationId); NativeAnimatedModule.stopAnimation(animationId);
}, },
setAnimatedNodeValue: function(nodeTag: number, value: number): void { setAnimatedNodeValue: function(nodeTag: ?number, value: ?number): void {
assertNativeAnimatedModule(); assertNativeAnimatedModule();
NativeAnimatedModule.setAnimatedNodeValue(nodeTag, value); NativeAnimatedModule.setAnimatedNodeValue(nodeTag, value);
}, },
setAnimatedNodeOffset: function(nodeTag: number, offset: number): void { setAnimatedNodeOffset: function(nodeTag: ?number, offset: ?number): void {
assertNativeAnimatedModule(); assertNativeAnimatedModule();
NativeAnimatedModule.setAnimatedNodeOffset(nodeTag, offset); NativeAnimatedModule.setAnimatedNodeOffset(nodeTag, offset);
}, },
flattenAnimatedNodeOffset: function(nodeTag: number): void { flattenAnimatedNodeOffset: function(nodeTag: ?number): void {
assertNativeAnimatedModule(); assertNativeAnimatedModule();
NativeAnimatedModule.flattenAnimatedNodeOffset(nodeTag); NativeAnimatedModule.flattenAnimatedNodeOffset(nodeTag);
}, },
extractAnimatedNodeOffset: function(nodeTag: number): void { extractAnimatedNodeOffset: function(nodeTag: ?number): void {
assertNativeAnimatedModule(); assertNativeAnimatedModule();
NativeAnimatedModule.extractAnimatedNodeOffset(nodeTag); NativeAnimatedModule.extractAnimatedNodeOffset(nodeTag);
}, },
connectAnimatedNodeToView: function(nodeTag: number, viewTag: number): void { connectAnimatedNodeToView: function(nodeTag: ?number, viewTag: ?number): void {
assertNativeAnimatedModule(); assertNativeAnimatedModule();
NativeAnimatedModule.connectAnimatedNodeToView(nodeTag, viewTag); NativeAnimatedModule.connectAnimatedNodeToView(nodeTag, viewTag);
}, },
disconnectAnimatedNodeFromView: function(nodeTag: number, viewTag: number): void { disconnectAnimatedNodeFromView: function(nodeTag: ?number, viewTag: ?number): void {
assertNativeAnimatedModule(); assertNativeAnimatedModule();
NativeAnimatedModule.disconnectAnimatedNodeFromView(nodeTag, viewTag); NativeAnimatedModule.disconnectAnimatedNodeFromView(nodeTag, viewTag);
}, },
dropAnimatedNode: function(tag: number): void { dropAnimatedNode: function(tag: ?number): void {
assertNativeAnimatedModule(); assertNativeAnimatedModule();
NativeAnimatedModule.dropAnimatedNode(tag); NativeAnimatedModule.dropAnimatedNode(tag);
}, },
addAnimatedEventToView: function(viewTag: number, eventName: string, eventMapping: EventMapping) { addAnimatedEventToView: function(viewTag: ?number, eventName: string, eventMapping: EventMapping) {
assertNativeAnimatedModule(); assertNativeAnimatedModule();
NativeAnimatedModule.addAnimatedEventToView(viewTag, eventName, eventMapping); NativeAnimatedModule.addAnimatedEventToView(viewTag, eventName, eventMapping);
}, },
removeAnimatedEventFromView(viewTag: number, eventName: string) { removeAnimatedEventFromView(viewTag: ?number, eventName: string) {
assertNativeAnimatedModule(); assertNativeAnimatedModule();
NativeAnimatedModule.removeAnimatedEventFromView(viewTag, eventName); NativeAnimatedModule.removeAnimatedEventFromView(viewTag, eventName);
} }

View File

@ -11,4 +11,4 @@
'use strict'; 'use strict';
module.exports = '16.0.0-alpha.2'; module.exports = '16.0.0-alpha.3';

View File

@ -53,6 +53,7 @@ function uncacheNode(inst) {
function uncacheFiberNode(tag) { function uncacheFiberNode(tag) {
delete instanceCache[tag]; delete instanceCache[tag];
delete instanceProps[tag];
} }
function getInstanceFromTag(tag) { function getInstanceFromTag(tag) {
@ -61,7 +62,9 @@ function getInstanceFromTag(tag) {
function getTagFromInstance(inst) { function getTagFromInstance(inst) {
// TODO (bvaughn) Clean up once Stack is deprecated // 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.'); invariant(tag, 'All native instances should have a tag.');
return tag; return tag;
} }

View File

@ -96,11 +96,7 @@ const NativeRenderer = ReactFiberReconciler({
}, },
appendInitialChild(parentInstance : Instance, child : Instance | TextInstance) : void { appendInitialChild(parentInstance : Instance, child : Instance | TextInstance) : void {
if (typeof child === 'number') { parentInstance._children.push(child);
parentInstance._children.push(child);
} else {
parentInstance._children.push(child);
}
}, },
commitTextUpdate( commitTextUpdate(

View File

@ -16,10 +16,10 @@ var ReactNativeGlobalResponderHandler = {
onChange: function(from, to, blockNativeResponder) { onChange: function(from, to, blockNativeResponder) {
if (to !== null) { if (to !== null) {
// TODO (bvaughn) Clean up once Stack is deprecated // TODO (bvaughn) Clean up once Stack is deprecated
UIManager.setJSResponder( var tag = typeof to.tag !== 'number'
to._rootNodeID || to.stateNode._nativeTag, ? to._rootNodeID
blockNativeResponder : to.stateNode._nativeTag;
); UIManager.setJSResponder(tag, blockNativeResponder);
} else { } else {
UIManager.clearJSResponder(); UIManager.clearJSResponder();
} }

View File

@ -15,6 +15,7 @@ var RCTEventEmitter;
var React; var React;
var ReactErrorUtils; var ReactErrorUtils;
var ReactNative; var ReactNative;
var ResponderEventPlugin;
var UIManager; var UIManager;
var createReactNativeComponentClass; var createReactNativeComponentClass;
@ -25,6 +26,7 @@ beforeEach(() => {
React = require('React'); React = require('React');
ReactErrorUtils = require('ReactErrorUtils'); ReactErrorUtils = require('ReactErrorUtils');
ReactNative = require('ReactNative'); ReactNative = require('ReactNative');
ResponderEventPlugin = require('ResponderEventPlugin');
UIManager = require('UIManager'); UIManager = require('UIManager');
createReactNativeComponentClass = require('createReactNativeComponentClass'); createReactNativeComponentClass = require('createReactNativeComponentClass');
@ -91,3 +93,97 @@ it('handles events', () => {
'outer touchend', '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(
<View id="parent">
<View key={1}>
<View
id="one"
onResponderEnd={() => log.push('one responder end')}
onResponderStart={() => log.push('one responder start')}
onStartShouldSetResponder={() => true}
/>
</View>
<View key={2}>
<View
id="two"
onResponderEnd={() => log.push('two responder end')}
onResponderStart={() => log.push('two responder start')}
onStartShouldSetResponder={() => true}
/>
</View>
</View>,
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(
<View id="parent">
<View key={2}>
<View
id="two"
onResponderEnd={() => log.push('two responder end')}
onResponderStart={() => log.push('two responder start')}
onStartShouldSetResponder={() => true}
/>
</View>
</View>,
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']);
});

View File

@ -27,13 +27,13 @@ type ReactNativeBaseComponentViewConfig = {
* @param {string} config iOS View configuration. * @param {string} config iOS View configuration.
* @private * @private
*/ */
const createReactNativeFiberComponentClass = function( const createReactNativeFiberComponentClass = function(
viewConfig: ReactNativeBaseComponentViewConfig viewConfig: ReactNativeBaseComponentViewConfig
): ReactClass<any> { ): ReactClass<any> {
// TODO(sema): This actually returns a string. Need to fix this before // TODO(sema): This actually returns a string. Need to fix this before
// we deploy Fiber. // we deploy Fiber.
return (ReactNativeViewConfigRegistry.register(viewConfig) : any); return (ReactNativeViewConfigRegistry.register(viewConfig) : any);
}; };
/** /**
* @param {string} config iOS View configuration. * @param {string} config iOS View configuration.
@ -59,6 +59,6 @@ const createReactNativeComponentClass = function(
return ((Constructor: any): ReactClass<any>); return ((Constructor: any): ReactClass<any>);
}; };
module.exports = (ReactNativeFeatureFlags.useFiber module.exports = ReactNativeFeatureFlags.useFiber
? createReactNativeFiberComponentClass ? createReactNativeFiberComponentClass
: createReactNativeComponentClass); : createReactNativeComponentClass;

View File

@ -53,8 +53,7 @@ import type { ReactInstance } from 'ReactInstanceType';
let injectedFindNode; let injectedFindNode;
let injectedFindRootNodeID; let injectedFindRootNodeID;
// TODO(sema): Type this more specifically. function findNodeHandle(componentOrHandle: any): ?number {
function findNodeHandle(componentOrHandle: any): any {
if (__DEV__) { if (__DEV__) {
// TODO: fix this unsafe cast to work with Fiber. // TODO: fix this unsafe cast to work with Fiber.
var owner = ((ReactCurrentOwner.current: any): ReactInstance | null); var owner = ((ReactCurrentOwner.current: any): ReactInstance | null);

View File

@ -223,8 +223,12 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
fiber._debugID = debugCounter++; fiber._debugID = debugCounter++;
fiber._debugSource = null; fiber._debugSource = null;
fiber._debugOwner = null; fiber._debugOwner = null;
if (typeof Object.preventExtensions === 'function') {
Object.preventExtensions(fiber);
}
} }
return fiber; return fiber;
}; };

View File

@ -67,7 +67,6 @@ var invariant = require('fbjs/lib/invariant');
if (__DEV__) { if (__DEV__) {
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
var warning = require('fbjs/lib/warning'); var warning = require('fbjs/lib/warning');
var warnedAboutStatelessRefs = {}; var warnedAboutStatelessRefs = {};
} }
@ -479,6 +478,15 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
// Proceed under the assumption that this is a functional component // Proceed under the assumption that this is a functional component
workInProgress.tag = FunctionalComponent; workInProgress.tag = FunctionalComponent;
if (__DEV__) { 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) { if (workInProgress.ref !== null) {
let info = ''; let info = '';
const ownerName = ReactDebugCurrentFiber.getCurrentFiberOwnerName(); const ownerName = ReactDebugCurrentFiber.getCurrentFiberOwnerName();

View File

@ -291,6 +291,7 @@ module.exports = function(
instance.props = props; instance.props = props;
instance.state = state; instance.state = state;
instance.refs = emptyObject;
instance.context = getMaskedContext(workInProgress, unmaskedContext); instance.context = getMaskedContext(workInProgress, unmaskedContext);
if (typeof instance.componentWillMount === 'function') { if (typeof instance.componentWillMount === 'function') {
@ -408,6 +409,19 @@ module.exports = function(
if (oldProps !== newProps || oldContext !== newContext) { if (oldProps !== newProps || oldContext !== newContext) {
if (typeof instance.componentWillReceiveProps === 'function') { if (typeof instance.componentWillReceiveProps === 'function') {
instance.componentWillReceiveProps(newProps, newContext); 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);
}
} }
} }

View File

@ -34,6 +34,7 @@ const {
if (__DEV__) { if (__DEV__) {
var checkReactTypeSpec = require('checkReactTypeSpec'); var checkReactTypeSpec = require('checkReactTypeSpec');
var ReactDebugCurrentFrame = require('react/lib/ReactDebugCurrentFrame');
var warnedAboutMissingGetChildContext = {}; var warnedAboutMissingGetChildContext = {};
} }
@ -91,7 +92,9 @@ exports.getMaskedContext = function(workInProgress : Fiber, unmaskedContext : Ob
if (__DEV__) { if (__DEV__) {
const name = getComponentName(workInProgress); 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. // 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. // 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. // TODO: remove this hack when we delete unstable_renderSubtree in Fiber.
const workInProgress = isReconciling ? fiber : null; 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}; return {...parentContext, ...childContext};
} }

View File

@ -12,15 +12,13 @@
'use strict'; 'use strict';
var warning = require('fbjs/lib/warning');
import type { Fiber } from 'ReactFiber'; import type { Fiber } from 'ReactFiber';
import type { FiberRoot } from 'ReactFiberRoot'; import type { FiberRoot } from 'ReactFiberRoot';
declare var __REACT_DEVTOOLS_GLOBAL_HOOK__ : Object | void; declare var __REACT_DEVTOOLS_GLOBAL_HOOK__ : Object | void;
if (__DEV__) {
var warning = require('fbjs/lib/warning');
}
let rendererID = null; let rendererID = null;
let injectInternals = null; let injectInternals = null;
let onCommitRoot = null; let onCommitRoot = null;

View File

@ -21,6 +21,7 @@ export type CapturedError = {
componentName : ?string, componentName : ?string,
componentStack : string, componentStack : string,
error : Error, error : Error,
errorBoundary : ?Object,
errorBoundaryFound : boolean, errorBoundaryFound : boolean,
errorBoundaryName : string | null, errorBoundaryName : string | null,
willRetry : boolean, willRetry : boolean,
@ -86,8 +87,21 @@ var {
var invariant = require('fbjs/lib/invariant'); var invariant = require('fbjs/lib/invariant');
if (__DEV__) { if (__DEV__) {
var warning = require('fbjs/lib/warning');
var ReactFiberInstrumentation = require('ReactFiberInstrumentation'); var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
var warnAboutUpdateOnUnmounted = function(instance : ReactClass<any>) {
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; var timeHeuristicForUnitOfWork = 1;
@ -349,6 +363,9 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P,
'in React. Please file an issue.' 'in React. Please file an issue.'
); );
// Reset this to null before calling lifecycles
ReactCurrentOwner.current = null;
// Updates that occur during the commit phase should have Task priority // Updates that occur during the commit phase should have Task priority
const previousPriorityContext = priorityContext; const previousPriorityContext = priorityContext;
priorityContext = TaskPriority; priorityContext = TaskPriority;
@ -941,6 +958,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P,
componentName, componentName,
componentStack, componentStack,
error, error,
errorBoundary: errorBoundaryFound ? boundary.stateNode : null,
errorBoundaryFound, errorBoundaryFound,
errorBoundaryName, errorBoundaryName,
willRetry, willRetry,
@ -1129,7 +1147,11 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P,
return; return;
} }
} else { } else {
// TODO: Warn about setting state on an unmounted component. if (__DEV__) {
if (fiber.tag === ClassComponent) {
warnAboutUpdateOnUnmounted(fiber.stateNode);
}
}
return; return;
} }
} }

View File

@ -15,13 +15,19 @@
import type { Fiber } from 'ReactFiber'; import type { Fiber } from 'ReactFiber';
var ReactInstanceMap = require('ReactInstanceMap'); var ReactInstanceMap = require('ReactInstanceMap');
var ReactCurrentOwner = require('react/lib/ReactCurrentOwner');
var invariant = require('fbjs/lib/invariant'); var invariant = require('fbjs/lib/invariant');
if (__DEV__) {
var warning = require('fbjs/lib/warning');
}
var { var {
HostRoot, HostRoot,
HostComponent, HostComponent,
HostText, HostText,
ClassComponent,
} = require('ReactTypeOfWork'); } = require('ReactTypeOfWork');
var { var {
@ -66,6 +72,24 @@ exports.isFiberMounted = function(fiber : Fiber) : boolean {
}; };
exports.isMounted = function(component : ReactComponent<any, any, any>) : boolean { exports.isMounted = function(component : ReactComponent<any, any, any>) : 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); var fiber : ?Fiber = ReactInstanceMap.get(component);
if (!fiber) { if (!fiber) {
return false; return false;
@ -243,7 +267,7 @@ exports.findCurrentHostFiber = function(parent : Fiber) : Fiber | null {
return null; return null;
}; };
exports.getComponentName = function(fiber: Fiber): string { function getComponentName(fiber: Fiber): string {
const type = fiber.type; const type = fiber.type;
const instance = fiber.stateNode; const instance = fiber.stateNode;
const constructor = instance && instance.constructor; const constructor = instance && instance.constructor;
@ -252,4 +276,5 @@ exports.getComponentName = function(fiber: Fiber): string {
type.name || (constructor && constructor.name) || type.name || (constructor && constructor.name) ||
'A Component' 'A Component'
); );
}; }
exports.getComponentName = getComponentName;

View File

@ -24,6 +24,7 @@ var ReactReconciler = require('ReactReconciler');
if (__DEV__) { if (__DEV__) {
var checkReactTypeSpec = require('checkReactTypeSpec'); var checkReactTypeSpec = require('checkReactTypeSpec');
var ReactDebugCurrentFrame = require('react/lib/ReactDebugCurrentFrame');
var warningAboutMissingGetChildContext = {}; var warningAboutMissingGetChildContext = {};
} }
@ -33,8 +34,6 @@ var shallowEqual = require('fbjs/lib/shallowEqual');
var shouldUpdateReactComponent = require('shouldUpdateReactComponent'); var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
var warning = require('fbjs/lib/warning'); var warning = require('fbjs/lib/warning');
import type { ReactPropTypeLocations } from 'ReactPropTypeLocations';
function StatelessComponent(Component) { function StatelessComponent(Component) {
} }
StatelessComponent.prototype.render = function() { StatelessComponent.prototype.render = function() {
@ -687,7 +686,7 @@ var ReactCompositeComponent = {
this._checkContextTypes( this._checkContextTypes(
Component.childContextTypes, Component.childContextTypes,
childContext, childContext,
'childContext' 'child context'
); );
} }
for (var name in childContext) { for (var name in childContext) {
@ -730,17 +729,17 @@ var ReactCompositeComponent = {
_checkContextTypes: function( _checkContextTypes: function(
typeSpecs, typeSpecs,
values, values,
location: ReactPropTypeLocations, location: string,
) { ) {
if (__DEV__) { if (__DEV__) {
ReactDebugCurrentFrame.current = this._debugID;
checkReactTypeSpec( checkReactTypeSpec(
typeSpecs, typeSpecs,
values, values,
location, location,
this.getName(), this.getName()
null,
this._debugID
); );
ReactDebugCurrentFrame.current = null;
} }
}, },
@ -850,6 +849,7 @@ var ReactCompositeComponent = {
// _pendingStateQueue which will ensure that any state updates gets // _pendingStateQueue which will ensure that any state updates gets
// immediately reconciled instead of waiting for the next batch. // immediately reconciled instead of waiting for the next batch.
if (willReceive && inst.componentWillReceiveProps) { if (willReceive && inst.componentWillReceiveProps) {
const beforeState = inst.state;
if (__DEV__) { if (__DEV__) {
measureLifeCyclePerf( measureLifeCyclePerf(
() => inst.componentWillReceiveProps(nextProps, nextContext), () => inst.componentWillReceiveProps(nextProps, nextContext),
@ -859,6 +859,20 @@ var ReactCompositeComponent = {
} else { } else {
inst.componentWillReceiveProps(nextProps, nextContext); 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 // If updating happens to enqueue any new updates, we shouldn't execute new

View File

@ -38,16 +38,12 @@ function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
if (!internalInstance) { if (!internalInstance) {
if (__DEV__) { if (__DEV__) {
var ctor = publicInstance.constructor; 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( warning(
!callerName, false,
'%s(...): Can only update a mounted or mounting component. ' + 'Can only update a mounted or mounting component. This usually means ' +
'This usually means you called %s() on an unmounted component. ' + 'you called setState, replaceState, or forceUpdate on an unmounted ' +
'This is a no-op.\n\nPlease check the code for the %s component.', 'component. This is a no-op.\n\nPlease check the code for the ' +
callerName, '%s component.',
callerName,
ctor && (ctor.displayName || ctor.name) || 'ReactClass' ctor && (ctor.displayName || ctor.name) || 'ReactClass'
); );
} }
@ -57,12 +53,10 @@ function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
if (__DEV__) { if (__DEV__) {
warning( warning(
ReactCurrentOwner.current == null, ReactCurrentOwner.current == null,
'%s(...): Cannot update during an existing state transition (such as ' + 'Cannot update during an existing state transition (such as within ' +
'within `render` or another component\'s constructor). Render methods ' + '`render` or another component\'s constructor). Render methods should ' +
'should be a pure function of props and state; constructor ' + 'be a pure function of props and state; constructor side-effects are ' +
'side-effects are an anti-pattern, but can be moved to ' + 'an anti-pattern, but can be moved to `componentWillMount`.',
'`componentWillMount`.',
callerName
); );
} }
@ -134,10 +128,7 @@ var ReactUpdateQueue = {
* @internal * @internal
*/ */
enqueueForceUpdate: function(publicInstance, callback, callerName) { enqueueForceUpdate: function(publicInstance, callback, callerName) {
var internalInstance = getInternalInstanceReadyForUpdate( var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
publicInstance,
'forceUpdate'
);
if (!internalInstance) { if (!internalInstance) {
return; return;
@ -174,10 +165,7 @@ var ReactUpdateQueue = {
* @internal * @internal
*/ */
enqueueReplaceState: function(publicInstance, completeState, callback, callerName) { enqueueReplaceState: function(publicInstance, completeState, callback, callerName) {
var internalInstance = getInternalInstanceReadyForUpdate( var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
publicInstance,
'replaceState'
);
if (!internalInstance) { if (!internalInstance) {
return; return;
@ -223,10 +211,7 @@ var ReactUpdateQueue = {
); );
} }
var internalInstance = getInternalInstanceReadyForUpdate( var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
publicInstance,
'setState'
);
if (!internalInstance) { if (!internalInstance) {
return; return;

View File

@ -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;

View File

@ -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';

View File

@ -11,124 +11,22 @@
'use strict'; 'use strict';
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames'); var checkPropTypes = require('react/lib/checkPropTypes');
var ReactPropTypesSecret = require('ReactPropTypesSecret'); 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( function checkReactTypeSpec(
typeSpecs, typeSpecs,
values, values,
location: ReactPropTypeLocations, location: string,
componentName, 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,
) { ) {
for (var typeSpecName in typeSpecs) { checkPropTypes(
if (typeSpecs.hasOwnProperty(typeSpecName)) { typeSpecs,
var error; values,
// Prop type validation may throw. In case they do, we don't want to location,
// fail the render phase where it didn't fail before. So we log it. componentName,
// After these have been cleaned up, we'll let them throw. getStackAddendum
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
);
}
}
}
} }
module.exports = checkReactTypeSpec; module.exports = checkReactTypeSpec;

View File

@ -10,17 +10,14 @@
*/ */
'use strict'; 'use strict';
var ReactPropTypeLocationNames = require('react/lib/ReactPropTypeLocationNames');
var normalizeColor = require('normalizeColor'); var normalizeColor = require('normalizeColor');
var colorPropType = function(isRequired, props, propName, componentName, location, propFullName) { var colorPropType = function(isRequired, props, propName, componentName, location, propFullName) {
var color = props[propName]; var color = props[propName];
if (color === undefined || color === null) { if (color === undefined || color === null) {
if (isRequired) { if (isRequired) {
var locationName = ReactPropTypeLocationNames[location];
return new Error( return new Error(
'Required ' + locationName + ' `' + (propFullName || propName) + 'Required ' + location + ' `' + (propFullName || propName) +
'` was not specified in `' + componentName + '`.' '` was not specified in `' + componentName + '`.'
); );
} }
@ -35,9 +32,8 @@ var colorPropType = function(isRequired, props, propName, componentName, locatio
} }
if (normalizeColor(color) === null) { if (normalizeColor(color) === null) {
var locationName = ReactPropTypeLocationNames[location];
return new Error( return new Error(
'Invalid ' + locationName + ' `' + (propFullName || propName) + 'Invalid ' + location + ' `' + (propFullName || propName) +
'` supplied to `' + componentName + '`: ' + color + '\n' + '` supplied to `' + componentName + '`: ' + color + '\n' +
`Valid color formats are `Valid color formats are
- '#f0f' (#rgb) - '#f0f' (#rgb)

View File

@ -127,7 +127,7 @@
"react-native": "local-cli/wrong-react-native.js" "react-native": "local-cli/wrong-react-native.js"
}, },
"peerDependencies": { "peerDependencies": {
"react": "16.0.0-alpha.2" "react": "~16.0.0-alpha.3"
}, },
"dependencies": { "dependencies": {
"absolute-path": "^0.0.0", "absolute-path": "^0.0.0",
@ -159,7 +159,7 @@
"debug": "^2.2.0", "debug": "^2.2.0",
"denodeify": "^1.2.1", "denodeify": "^1.2.1",
"event-target-shim": "^1.0.5", "event-target-shim": "^1.0.5",
"fbjs": "^0.8.9", "fbjs": "~0.8.9",
"fbjs-scripts": "^0.7.0", "fbjs-scripts": "^0.7.0",
"form-data": "^2.1.1", "form-data": "^2.1.1",
"fs-extra": "^0.26.2", "fs-extra": "^0.26.2",
@ -222,9 +222,9 @@
"jest-repl": "19.0.2", "jest-repl": "19.0.2",
"jest-runtime": "19.0.2", "jest-runtime": "19.0.2",
"mock-fs": "^3.11.0", "mock-fs": "^3.11.0",
"react": "16.0.0-alpha.2", "react": "~16.0.0-alpha.3",
"react-dom": "16.0.0-alpha.2", "react-dom": "~16.0.0-alpha.3",
"react-test-renderer": "16.0.0-alpha.2", "react-test-renderer": "~16.0.0-alpha.3",
"shelljs": "0.6.0", "shelljs": "0.6.0",
"sinon": "^2.0.0-pre.2" "sinon": "^2.0.0-pre.2"
} }