Require instantiable components to extend React.Component
Ben Alpert committed
Aug 19, 2015 at 15:34 UTC
f3b7749c373093dcb7e576023c45fb628c695740
11 files changed
+73
-55
src/addons/__tests__/ReactFragment-test.js
+1
-1
@@ -40,7 +40,7 @@ describe('ReactFragment', function() {
40
});
41
42
it('should throw if a plain object even if it is in an owner', function() {
43
- class Foo {
43
+ class Foo extends React.Component {
44
render() {
45
var children = {
46
a: <span />,
src/isomorphic/classic/class/ReactClass.js
+1
@@ -847,6 +847,7 @@ var ReactClass = {
847
};
848
Constructor.prototype = new ReactClassComponent();
849
Constructor.prototype.constructor = Constructor;
850
+ Constructor.isReactClass = {};
851
852
injectedMixins.forEach(
853
mixSpecIntoComponent.bind(null, Constructor)
src/isomorphic/modern/class/ReactComponent.js
+2
@@ -29,6 +29,8 @@ function ReactComponent(props, context, updater) {
29
this.updater = updater || ReactNoopUpdateQueue;
30
}
31
32
+ReactComponent.isReactClass = {};
33
+
34
/**
35
* Sets a subset of the state. Always use this to mutate
36
* state. You should treat `this.state` as immutable.
src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee
+8
-8
@@ -55,7 +55,7 @@ describe 'ReactCoffeeScriptClass', ->
55
expect(console.error.calls[0].args[0]).toContain('No `render` method found on the returned component instance')
56
57
it 'renders a simple stateless component with prop', ->
58
- class Foo
58
+ class Foo extends React.Component
59
render: ->
60
Inner
61
name: @props.bar
@@ -144,7 +144,7 @@ describe 'ReactCoffeeScriptClass', ->
144
145
it 'should throw with non-object in the initial state property', ->
146
[['an array'], 'a string', 1234].forEach (state) ->
147
- class Foo
147
+ class Foo extends React.Component
148
constructor: ->
149
@state = state
150
@@ -220,7 +220,7 @@ describe 'ReactCoffeeScriptClass', ->
220
221
it 'will call all the normal life cycle methods', ->
222
lifeCycles = []
223
- class Foo
223
+ class Foo extends React.Component
224
constructor: ->
225
@state = {}
226
@@ -308,7 +308,7 @@ describe 'ReactCoffeeScriptClass', ->
308
309
it 'should warn when misspelling shouldComponentUpdate', ->
310
spyOn console, 'error'
311
- class NamedComponent
311
+ class NamedComponent extends React.Component
312
componentShouldUpdate: ->
313
false
314
@@ -326,7 +326,7 @@ describe 'ReactCoffeeScriptClass', ->
326
327
it 'should warn when misspelling componentWillReceiveProps', ->
328
spyOn console, 'error'
329
- class NamedComponent
329
+ class NamedComponent extends React.Component
330
componentWillRecieveProps: ->
331
false
332
@@ -368,13 +368,13 @@ describe 'ReactCoffeeScriptClass', ->
368
)
369
370
it 'supports this.context passed via getChildContext', ->
371
- class Bar
371
+ class Bar extends React.Component
372
@contextTypes:
373
bar: React.PropTypes.string
374
render: ->
375
div className: @context.bar
376
377
- class Foo
377
+ class Foo extends React.Component
378
@childContextTypes:
379
bar: React.PropTypes.string
380
getChildContext: ->
@@ -385,7 +385,7 @@ describe 'ReactCoffeeScriptClass', ->
385
test React.createElement(Foo), 'DIV', 'bar-through-context'
386
387
it 'supports classic refs', ->
388
- class Foo
388
+ class Foo extends React.Component
389
render: ->
390
Inner
391
name: 'foo'
src/isomorphic/modern/class/__tests__/ReactES6Class-test.js
+14
-13
@@ -63,16 +63,15 @@ describe('ReactES6Class', function() {
63
64
expect(console.error.calls.length).toBe(1);
65
expect(console.error.calls[0].args[0]).toBe(
66
- 'Warning: Foo(...): ' +
67
- 'No `render` method found on the returned component instance: you may ' +
68
- 'have forgotten to define `render` in your component or you may have ' +
69
- 'accidentally tried to render an element whose type is a function that ' +
70
- 'isn\'t a React component.'
66
+ 'Warning: Foo(...): No `render` method found on the returned component ' +
67
+ 'instance: you may have forgotten to define `render`, returned ' +
68
+ 'null/false from a stateless component, or tried to render an element ' +
69
+ 'whose type is a function that isn\'t a React component.'
70
);
71
});
72
73
it('renders a simple stateless component with prop', function() {
75
- class Foo {
74
+ class Foo extends React.Component {
75
render() {
76
return <Inner name={this.props.bar} />;
77
}
@@ -167,8 +166,9 @@ describe('ReactES6Class', function() {
166
167
it('should throw with non-object in the initial state property', function() {
168
[['an array'], 'a string', 1234].forEach(function(state) {
170
- class Foo {
169
+ class Foo extends React.Component {
170
constructor() {
171
+ super();
172
this.state = state;
173
}
174
render() {
@@ -266,8 +266,9 @@ describe('ReactES6Class', function() {
266
267
it('will call all the normal life cycle methods', function() {
268
var lifeCycles = [];
269
- class Foo {
269
+ class Foo extends React.Component {
270
constructor() {
271
+ super();
272
this.state = {};
273
}
274
componentWillMount() {
@@ -360,7 +361,7 @@ describe('ReactES6Class', function() {
361
it('should warn when misspelling shouldComponentUpdate', function() {
362
spyOn(console, 'error');
363
363
- class NamedComponent {
364
+ class NamedComponent extends React.Component {
365
componentShouldUpdate() {
366
return false;
367
}
@@ -382,7 +383,7 @@ describe('ReactES6Class', function() {
383
it('should warn when misspelling componentWillReceiveProps', function() {
384
spyOn(console, 'error');
385
385
- class NamedComponent {
386
+ class NamedComponent extends React.Component {
387
componentWillRecieveProps() {
388
return false;
389
}
@@ -428,13 +429,13 @@ describe('ReactES6Class', function() {
429
});
430
431
it('supports this.context passed via getChildContext', function() {
431
- class Bar {
432
+ class Bar extends React.Component {
433
render() {
434
return <div className={this.context.bar} />;
435
}
436
}
437
Bar.contextTypes = {bar: React.PropTypes.string};
437
- class Foo {
438
+ class Foo extends React.Component {
439
getChildContext() {
440
return {bar: 'bar-through-context'};
441
}
@@ -447,7 +448,7 @@ describe('ReactES6Class', function() {
448
});
449
450
it('supports classic refs', function() {
450
- class Foo {
451
+ class Foo extends React.Component {
452
render() {
453
return <Inner name="foo" ref="inner" />;
454
}
src/isomorphic/modern/class/__tests__/ReactTypeScriptClass-test.ts
+13
-14
@@ -45,7 +45,7 @@ function test(element, expectedTag, expectedClassName) {
45
class Empty extends React.Component { }
46
47
// it renders a simple stateless component with prop
48
-class SimpleStateless {
48
+class SimpleStateless extends React.Component {
49
props : any;
50
render() {
51
return React.createElement(Inner, {name: this.props.bar});
@@ -124,19 +124,19 @@ class RenderOnce extends React.Component {
124
}
125
126
// it should throw with non-object in the initial state property
127
-class ArrayState {
127
+class ArrayState extends React.Component {
128
state = ['an array'];
129
render() {
130
return React.createElement('span');
131
}
132
}
133
-class StringState {
133
+class StringState extends React.Component {
134
state = 'a string';
135
render() {
136
return React.createElement('span');
137
}
138
}
139
-class NumberState {
139
+class NumberState extends React.Component {
140
state = 1234;
141
render() {
142
return React.createElement('span');
@@ -203,7 +203,7 @@ class ForceUpdateWithNoState extends React.Component {
203
204
// it will call all the normal life cycle methods
205
var lifeCycles = [];
206
-class NormalLifeCycles {
206
+class NormalLifeCycles extends React.Component {
207
props : any;
208
state = {};
209
componentWillMount() {
@@ -254,7 +254,7 @@ class ClassicProperties extends React.Component {
254
}
255
256
// it should warn when misspelling shouldComponentUpdate
257
-class MisspelledComponent1 {
257
+class MisspelledComponent1 extends React.Component {
258
componentShouldUpdate() {
259
return false;
260
}
@@ -264,7 +264,7 @@ class MisspelledComponent1 {
264
}
265
266
// it should warn when misspelling componentWillReceiveProps
267
-class MisspelledComponent2 {
267
+class MisspelledComponent2 extends React.Component {
268
componentWillRecieveProps() {
269
return false;
270
}
@@ -280,7 +280,7 @@ class ReadContext extends React.Component {
280
return React.createElement('div', { className: this.context.bar });
281
}
282
}
283
-class ProvideContext {
283
+class ProvideContext extends React.Component {
284
static childContextTypes = { bar: React.PropTypes.string };
285
getChildContext() {
286
return { bar: 'bar-through-context' };
@@ -291,7 +291,7 @@ class ProvideContext {
291
}
292
293
// it supports classic refs
294
-class ClassicRefs {
294
+class ClassicRefs extends React.Component {
295
render() {
296
return React.createElement(Inner, {name: 'foo', ref: 'inner'});
297
}
@@ -318,11 +318,10 @@ describe('ReactTypeScriptClass', function() {
318
319
expect((<any>console.error).argsForCall.length).toBe(1);
320
expect((<any>console.error).argsForCall[0][0]).toBe(
321
- 'Warning: Empty(...): ' +
322
- 'No `render` method found on the returned component instance: you may ' +
323
- 'have forgotten to define `render` in your component or you may have ' +
324
- 'accidentally tried to render an element whose type is a function that ' +
325
- 'isn\'t a React component.'
321
+ 'Warning: Empty(...): No `render` method found on the returned ' +
322
+ 'component instance: you may have forgotten to define `render`, ' +
323
+ 'returned null/false from a stateless component, or tried to render an ' +
324
+ 'element whose type is a function that isn\'t a React component.'
325
);
326
});
327
src/isomorphic/modern/element/__tests__/ReactJSXElement-test.js
+2
-2
@@ -24,7 +24,7 @@ describe('ReactJSXElement', function() {
24
React = require('React');
25
ReactDOM = require('ReactDOM');
26
ReactTestUtils = require('ReactTestUtils');
27
- Component = class {
27
+ Component = class extends React.Component {
28
render() {
29
return <div />;
30
}
@@ -176,7 +176,7 @@ describe('ReactJSXElement', function() {
176
});
177
178
it('should normalize props with default values', function() {
179
- class NormalizingComponent {
179
+ class NormalizingComponent extends React.Component {
180
render() {
181
return <span>{this.props.prop}</span>;
182
}
src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js
+9
-9
@@ -27,13 +27,13 @@ describe('ReactJSXElementValidator', function() {
27
React = require('React');
28
ReactTestUtils = require('ReactTestUtils');
29
30
- Component = class {
30
+ Component = class extends React.Component {
31
render() {
32
return <div />;
33
}
34
};
35
36
- RequiredPropComponent = class {
36
+ RequiredPropComponent = class extends React.Component {
37
render() {
38
return <span>{this.props.prop}</span>;
39
}
@@ -56,13 +56,13 @@ describe('ReactJSXElementValidator', function() {
56
it('warns for keys for arrays of elements with owner info', function() {
57
spyOn(console, 'error');
58
59
- class InnerComponent {
59
+ class InnerComponent extends React.Component {
60
render() {
61
return <Component>{this.props.childSet}</Component>;
62
}
63
}
64
65
- class ComponentWrapper {
65
+ class ComponentWrapper extends React.Component {
66
render() {
67
return (
68
<InnerComponent
@@ -181,7 +181,7 @@ describe('ReactJSXElementValidator', function() {
181
// component, we give a small hint as to which parent instantiated that
182
// component as per warnings about key usage in ReactElementValidator.
183
spyOn(console, 'error');
184
- class MyComp {
184
+ class MyComp extends React.Component {
185
render() {
186
return <div>My color is {this.color}</div>;
187
}
@@ -189,7 +189,7 @@ describe('ReactJSXElementValidator', function() {
189
MyComp.propTypes = {
190
color: React.PropTypes.string,
191
};
192
- class ParentComp {
192
+ class ParentComp extends React.Component {
193
render() {
194
return <MyComp color={123} />;
195
}
@@ -290,7 +290,7 @@ describe('ReactJSXElementValidator', function() {
290
// actually occurs. Since this step is skipped in production, we should just
291
// warn instead of throwing for this case.
292
spyOn(console, 'error');
293
- class NullPropTypeComponent {
293
+ class NullPropTypeComponent extends React.Component {
294
render() {
295
return <span>{this.props.prop}</span>;
296
}
@@ -308,7 +308,7 @@ describe('ReactJSXElementValidator', function() {
308
309
it('should warn on invalid context types', function() {
310
spyOn(console, 'error');
311
- class NullContextTypeComponent {
311
+ class NullContextTypeComponent extends React.Component {
312
render() {
313
return <span>{this.props.prop}</span>;
314
}
@@ -326,7 +326,7 @@ describe('ReactJSXElementValidator', function() {
326
327
it('should warn if getDefaultProps is specificed on the class', function() {
328
spyOn(console, 'error');
329
- class GetDefaultPropsComponent {
329
+ class GetDefaultPropsComponent extends React.Component {
330
render() {
331
return <span>{this.props.prop}</span>;
332
}
src/renderers/dom/client/ReactMount.js
+4
@@ -373,6 +373,10 @@ function hasNonRootReactChild(node) {
373
* here.
374
*/
375
var TopLevelWrapper = function() {};
376
+TopLevelWrapper.isReactClass = {};
377
+if (__DEV__) {
378
+ TopLevelWrapper.displayName = 'TopLevelWrapper';
379
+}
380
TopLevelWrapper.prototype.render = function() {
381
// this.props is actually a ReactElement
382
return this.props;
src/renderers/shared/reconciler/ReactCompositeComponent.js
+18
-8
@@ -155,14 +155,24 @@ var ReactCompositeComponentMixin = {
155
if (__DEV__) {
156
// This will throw later in _renderValidatedComponent, but add an early
157
// warning now to help debugging
158
- warning(
159
- inst.render != null,
160
- '%s(...): No `render` method found on the returned component ' +
161
- 'instance: you may have forgotten to define `render` in your ' +
162
- 'component or you may have accidentally tried to render an element ' +
163
- 'whose type is a function that isn\'t a React component.',
164
- Component.displayName || Component.name || 'Component'
165
- );
158
+ if (inst.render == null) {
159
+ warning(
160
+ false,
161
+ '%s(...): No `render` method found on the returned component ' +
162
+ 'instance: you may have forgotten to define `render`, returned ' +
163
+ 'null/false from a stateless component, or tried to render an ' +
164
+ 'element whose type is a function that isn\'t a React component.',
165
+ Component.displayName || Component.name || 'Component'
166
+ );
167
+ } else {
168
+ // We support ES6 inheriting from React.Component, the module pattern,
169
+ // and stateless components, but not ES6 classes that don't extend
170
+ warning(
171
+ Component.isReactClass || !(inst instanceof Component),
172
+ '%s(...): React component classes must extend React.Component.',
173
+ Component.displayName || Component.name || 'Component'
174
+ );
175
+ }
176
}
177
178
// These should be set up in the constructor, but as a convenience for
src/renderers/shared/reconciler/ReactEmptyComponent.js
+1
@@ -28,6 +28,7 @@ var ReactEmptyComponentInjection = {
28
};
29
30
var ReactEmptyComponentType = function() {};
31
+ReactEmptyComponentType.isReactClass = {};
32
ReactEmptyComponentType.prototype.componentDidMount = function() {
33
var internalInstance = ReactInstanceMap.get(this);
34
// TODO: Make sure we run these methods in the correct order, we shouldn't