[Fiber] Complete ES6 Class related errors support (#8156)

* Print a warning in fiber for lacking render method

* Reject initial non-object state in Fiber

Rejects Arrays, Strings & Numbers. Allows nulls.

* Centralize fiber checks on ES6 Classes

* Add classic & instance property checks on fiber

- check the absence of getInitialState & getDefaultProps as methods
- check the absence of propTypes & contextTypes at instance-level

* Convert to normalized React `warning` calls

* Support lifecycle typo detection in fiber

* Get the complete warnings from the existing code

* Only check classInstance once

Avoid rechecking while resuming

* Can warn component while resuming but only once

This is achieved by tracking the warning state in the Fiber structure.

* Remove warning deduplication

* Factor name retrieval in a getName helper

* Use invariant instead of throw

* Read inst.state only once for the invariant

* Fix condition on the instance state

* Register failing/passing fiber tests
This commit is contained in:
Robin Ricard
2016-11-05 11:19:48 -07:00
committed by Sebastian Markbåge
parent 2e91a8395f
commit 430c49b9c8
3 changed files with 114 additions and 16 deletions
-16
View File
@@ -48,30 +48,15 @@ src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js
* includes the owner name when passing null, undefined, boolean, or number
src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee
* throws if no render function is defined
* renders based on context in the constructor
* should throw with non-object in the initial state property
* warns when classic properties are defined on the instance, but does not invoke them.
* should warn when misspelling shouldComponentUpdate
* should warn when misspelling componentWillReceiveProps
* supports this.context passed via getChildContext
src/isomorphic/modern/class/__tests__/ReactES6Class-test.js
* throws if no render function is defined
* renders based on context in the constructor
* should throw with non-object in the initial state property
* warns when classic properties are defined on the instance, but does not invoke them.
* should warn when misspelling shouldComponentUpdate
* should warn when misspelling componentWillReceiveProps
* supports this.context passed via getChildContext
src/isomorphic/modern/class/__tests__/ReactTypeScriptClass-test.ts
* throws if no render function is defined
* renders based on context in the constructor
* should throw with non-object in the initial state property
* warns when classic properties are defined on the instance, but does not invoke them.
* should warn when misspelling shouldComponentUpdate
* should warn when misspelling componentWillReceiveProps
* supports this.context passed via getChildContext
src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js
@@ -538,7 +523,6 @@ src/renderers/shared/stack/reconciler/__tests__/ReactCompositeComponent-test.js
* should warn about `setState` in render
* should warn about `setState` in getChildContext
* should warn when shouldComponentUpdate() returns undefined
* should warn when componentDidUnmount method is defined
* should pass context to children when not owner
* should pass context when re-rendered for static child
* should pass context when re-rendered for static child within a composite component
+16
View File
@@ -350,30 +350,40 @@ src/isomorphic/modern/class/__tests__/ReactClassEquivalence-test.js
src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee
* preserves the name of the class for use in error messages
* throws if no render function is defined
* renders a simple stateless component with prop
* renders based on state using initial values in this.props
* renders based on state using props in the constructor
* renders only once when setting state in componentWillMount
* should throw with non-object in the initial state property
* should render with null in the initial state property
* setState through an event handler
* should not implicitly bind event handlers
* renders using forceUpdate even when there is no state
* will call all the normal life cycle methods
* warns when classic properties are defined on the instance, but does not invoke them.
* should warn when misspelling shouldComponentUpdate
* should warn when misspelling componentWillReceiveProps
* should throw AND warn when trying to access classic APIs
* supports classic refs
* supports drilling through to the DOM using findDOMNode
src/isomorphic/modern/class/__tests__/ReactES6Class-test.js
* preserves the name of the class for use in error messages
* throws if no render function is defined
* renders a simple stateless component with prop
* renders based on state using initial values in this.props
* renders based on state using props in the constructor
* renders only once when setting state in componentWillMount
* should throw with non-object in the initial state property
* should render with null in the initial state property
* setState through an event handler
* should not implicitly bind event handlers
* renders using forceUpdate even when there is no state
* will call all the normal life cycle methods
* warns when classic properties are defined on the instance, but does not invoke them.
* should warn when misspelling shouldComponentUpdate
* should warn when misspelling componentWillReceiveProps
* should throw AND warn when trying to access classic APIs
* supports classic refs
* supports drilling through to the DOM using findDOMNode
@@ -385,15 +395,20 @@ src/isomorphic/modern/class/__tests__/ReactPureComponent-test.js
src/isomorphic/modern/class/__tests__/ReactTypeScriptClass-test.ts
* preserves the name of the class for use in error messages
* throws if no render function is defined
* renders a simple stateless component with prop
* renders based on state using initial values in this.props
* renders based on state using props in the constructor
* renders only once when setting state in componentWillMount
* should throw with non-object in the initial state property
* should render with null in the initial state property
* setState through an event handler
* should not implicitly bind event handlers
* renders using forceUpdate even when there is no state
* will call all the normal life cycle methods
* warns when classic properties are defined on the instance, but does not invoke them.
* should warn when misspelling shouldComponentUpdate
* should warn when misspelling componentWillReceiveProps
* should throw AND warn when trying to access classic APIs
* supports classic refs
* supports drilling through to the DOM using findDOMNode
@@ -1011,6 +1026,7 @@ src/renderers/shared/stack/reconciler/__tests__/ReactCompositeComponent-test.js
* should silently allow `setState`, not call cb on unmounting components
* should cleanup even if render() fatals
* should call componentWillUnmount before unmounting
* should warn when componentDidUnmount method is defined
* should skip update when rerendering element in container
* only renders once if updated in componentWillReceiveProps
* should allow access to findDOMNode in componentWillUnmount
@@ -24,6 +24,11 @@ var {
var { isMounted } = require('ReactFiberTreeReflection');
var ReactInstanceMap = require('ReactInstanceMap');
var shallowEqual = require('shallowEqual');
var warning = require('warning');
var invariant = require('invariant');
const isArray = Array.isArray;
module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
@@ -91,6 +96,98 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
return true;
}
function getName(workInProgress: Fiber, inst: any): string {
const type = workInProgress.type;
const constructor = inst && inst.constructor;
return (
type.displayName || (constructor && constructor.displayName) ||
type.name || (constructor && constructor.name) ||
'A Component'
);
}
function checkClassInstance(workInProgress: Fiber, inst: any) {
if (__DEV__) {
const name = getName(workInProgress, inst);
const renderPresent = inst.render;
warning(
renderPresent,
'%s(...): No `render` method found on the returned component ' +
'instance: you may have forgotten to define `render`.',
name
);
const noGetInitialStateOnES6 = (
!inst.getInitialState ||
inst.getInitialState.isReactClassApproved
);
warning(
noGetInitialStateOnES6,
'getInitialState was defined on %s, a plain JavaScript class. ' +
'This is only supported for classes created using React.createClass. ' +
'Did you mean to define a state property instead?',
name
);
const noGetDefaultPropsOnES6 = (
!inst.getDefaultProps ||
inst.getDefaultProps.isReactClassApproved
);
warning(
noGetDefaultPropsOnES6,
'getDefaultProps was defined on %s, a plain JavaScript class. ' +
'This is only supported for classes created using React.createClass. ' +
'Use a static property to define defaultProps instead.',
name
);
const noInstancePropTypes = !inst.propTypes;
warning(
noInstancePropTypes,
'propTypes was defined as an instance property on %s. Use a static ' +
'property to define propTypes instead.',
name,
);
const noInstanceContextTypes = !inst.contextTypes;
warning(
noInstanceContextTypes,
'contextTypes was defined as an instance property on %s. Use a static ' +
'property to define contextTypes instead.',
name,
);
const noComponentShouldUpdate = typeof inst.componentShouldUpdate !== 'function';
warning(
noComponentShouldUpdate,
'%s has a method called ' +
'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
'The name is phrased as a question because the function is ' +
'expected to return a value.',
name
);
const noComponentDidUnmount = typeof inst.componentDidUnmount !== 'function';
warning(
noComponentDidUnmount,
'%s has a method called ' +
'componentDidUnmount(). But there is no such lifecycle method. ' +
'Did you mean componentWillUnmount()?',
name
);
const noComponentWillRecieveProps = typeof inst.componentWillRecieveProps !== 'function';
warning(
noComponentWillRecieveProps,
'%s has a method called ' +
'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
name
);
}
const instanceState = inst.state;
if (instanceState && (typeof instanceState !== 'object' || isArray(instanceState))) {
invariant(
false,
'%s.state: must be set to an object or null',
getName(workInProgress, inst)
);
}
}
function adoptClassInstance(workInProgress : Fiber, instance : any) : void {
instance.updater = updater;
workInProgress.stateNode = instance;
@@ -102,6 +199,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
const ctor = workInProgress.type;
const props = workInProgress.pendingProps;
const instance = new ctor(props);
checkClassInstance(workInProgress, instance);
adoptClassInstance(workInProgress, instance);
return instance;
}