Enable null return values in plain functions
jim committed
Jan 19, 2016 at 13:11 UTC
757756f682ab11961479e3c10adb0444271fdb9e
5 files changed
+56
-91
src/isomorphic/modern/class/__tests__/ReactES6Class-test.js
+1
-3
@@ -64,9 +64,7 @@ describe('ReactES6Class', function() {
64
expect(console.error.calls.length).toBe(1);
65
expect(console.error.argsForCall[0][0]).toBe(
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.'
67
+ 'instance: you may have forgotten to define `render`.'
68
);
69
});
70
src/isomorphic/modern/class/__tests__/ReactTypeScriptClass-test.ts
+1
-3
@@ -319,9 +319,7 @@ describe('ReactTypeScriptClass', function() {
319
expect((<any>console.error).argsForCall.length).toBe(1);
320
expect((<any>console.error).argsForCall[0][0]).toBe(
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.'
322
+ 'component instance: you may have forgotten to define `render`.'
323
);
324
});
325
src/renderers/shared/reconciler/ReactCompositeComponent.js
+13
-25
@@ -43,7 +43,16 @@ function StatelessComponent(Component) {
43
}
44
StatelessComponent.prototype.render = function() {
45
var Component = ReactInstanceMap.get(this)._currentElement.type;
46
- return Component(this.props, this.context, this.updater);
46
+ var element = Component(this.props, this.context, this.updater);
47
+ if (__DEV__) {
48
+ warning(
49
+ element === null || element === false || ReactElement.isValidElement(element),
50
+ '%s must be a class extending React.Component or be a stateless ' +
51
+ 'function that returns a valid React element.',
52
+ Component.displayName || Component.name || 'Component'
53
+ );
54
+ }
55
+ return element;
56
};
57
58
/**
@@ -147,13 +156,7 @@ var ReactCompositeComponentMixin = {
156
var inst;
157
var renderedElement;
158
150
- // This is a way to detect if Component is a stateless arrow function
151
- // component, which is not newable. It might not be 100% reliable but is
152
- // something we can do until we start detecting that Component extends
153
- // React.Component. We already assume that typeof Component === 'function'.
154
- var canInstantiate = 'prototype' in Component;
155
-
156
- if (canInstantiate) {
159
+ if (Component.prototype && Component.prototype.isReactComponent) {
160
if (__DEV__) {
161
ReactCurrentOwner.current = this;
162
try {
@@ -164,10 +167,7 @@ var ReactCompositeComponentMixin = {
167
} else {
168
inst = new Component(publicProps, publicContext, ReactUpdateQueue);
169
}
167
- }
168
-
169
- if (!canInstantiate || inst === null || inst === false || ReactElement.isValidElement(inst)) {
170
- renderedElement = inst;
170
+ } else {
171
inst = new StatelessComponent(Component);
172
}
173
@@ -178,19 +178,7 @@ var ReactCompositeComponentMixin = {
178
warning(
179
false,
180
'%s(...): No `render` method found on the returned component ' +
181
- 'instance: you may have forgotten to define `render`, returned ' +
182
- 'null/false from a stateless component, or tried to render an ' +
183
- 'element whose type is a function that isn\'t a React component.',
184
- Component.displayName || Component.name || 'Component'
185
- );
186
- } else {
187
- // We support ES6 inheriting from React.Component, the module pattern,
188
- // and stateless components, but not ES6 classes that don't extend
189
- warning(
190
- (Component.prototype && Component.prototype.isReactComponent) ||
191
- !canInstantiate ||
192
- !(inst instanceof Component),
193
- '%s(...): React component classes must extend React.Component.',
181
+ 'instance: you may have forgotten to define `render`.',
182
Component.displayName || Component.name || 'Component'
183
);
184
}
src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js
-44
@@ -1103,19 +1103,6 @@ describe('ReactCompositeComponent', function() {
1103
expect(a).toBe(b);
1104
});
1105
1106
- it('should warn when using non-React functions in JSX', function() {
1107
- function NotAComponent() {
1108
- return [<div />, <div />];
1109
- }
1110
- expect(function() {
1111
- ReactTestUtils.renderIntoDocument(<div><NotAComponent /></div>);
1112
- }).toThrow(); // has no method 'render'
1113
- expect(console.error.calls.length).toBe(1);
1114
- expect(console.error.argsForCall[0][0]).toContain(
1115
- 'NotAComponent(...): No `render` method found'
1116
- );
1117
- });
1118
-
1106
it('context should be passed down from the parent', function() {
1107
var Parent = React.createClass({
1108
childContextTypes: {
@@ -1247,37 +1234,6 @@ describe('ReactCompositeComponent', function() {
1234
expect(console.error.calls.length).toBe(0);
1235
});
1236
1250
- it('should warn when a class does not extend React.Component', function() {
1251
-
1252
- var container = document.createElement('div');
1253
-
1254
- class Foo {
1255
- render() {
1256
- return <span />;
1257
- }
1258
- }
1259
-
1260
- function Bar() { }
1261
- Bar.prototype = Object.create(React.Component.prototype);
1262
- Bar.prototype.render = function() {
1263
- return <span />;
1264
- };
1265
-
1266
- expect(console.error.calls.length).toBe(0);
1267
-
1268
- ReactDOM.render(<Bar />, container);
1269
-
1270
- expect(console.error.calls.length).toBe(0);
1271
-
1272
- ReactDOM.render(<Foo />, container);
1273
-
1274
- expect(console.error.calls.length).toBe(1);
1275
- expect(console.error.argsForCall[0][0]).toContain(
1276
- 'React component classes must extend React.Component'
1277
- );
1278
-
1279
- });
1280
-
1237
it('should warn when mutated props are passed', function() {
1238
1239
var container = document.createElement('div');
src/renderers/shared/reconciler/__tests__/ReactStatelessComponent-test.js
+41
-16
@@ -98,19 +98,19 @@ describe('ReactStatelessComponent', function() {
98
expect(el.textContent).toBe('mest');
99
});
100
101
- it('should support module pattern components', function() {
102
- function Child({test}) {
103
- return {
104
- render() {
105
- return <div>{test}</div>;
106
- },
107
- };
101
+ it('should warn when stateless component returns array', function() {
102
+ spyOn(console, 'error');
103
+ function NotAComponent() {
104
+ return [<div />, <div />];
105
}
109
-
110
- var el = document.createElement('div');
111
- ReactDOM.render(<Child test="test" />, el);
112
-
113
- expect(el.textContent).toBe('test');
106
+ expect(function() {
107
+ ReactTestUtils.renderIntoDocument(<div><NotAComponent /></div>);
108
+ }).toThrow();
109
+ expect(console.error.calls.length).toBe(1);
110
+ expect(console.error.argsForCall[0][0]).toContain(
111
+ 'NotAComponent must be a class extending React.Component or be a stateless ' +
112
+ 'function that returns a valid React element.'
113
+ );
114
});
115
116
it('should throw on string refs in pure functions', function() {
@@ -204,10 +204,6 @@ describe('ReactStatelessComponent', function() {
204
});
205
206
it('should work with arrow functions', function() {
207
- // TODO: actually use arrow functions, probably need node v4 and maybe
208
- // a separate file that we blacklist from the arrow function transform.
209
- // We can't actually test this without native arrow functions since the
210
- // issues (non-newable) don't apply to any other functions.
207
var Child = function() {
208
return <div />;
209
};
@@ -217,4 +213,33 @@ describe('ReactStatelessComponent', function() {
213
214
expect(() => ReactTestUtils.renderIntoDocument(<Child />)).not.toThrow();
215
});
216
+
217
+ it('should allow simple functions to return null', function() {
218
+ var Child = function() {
219
+ return null;
220
+ };
221
+ expect(() => ReactTestUtils.renderIntoDocument(<Child />)).not.toThrow();
222
+ });
223
+
224
+ it('should allow simple functions to return false', function() {
225
+ function Child() {
226
+ return false;
227
+ };
228
+ expect(() => ReactTestUtils.renderIntoDocument(<Child />)).not.toThrow();
229
+ });
230
+
231
+ it('should warn when using non-React functions in JSX', function() {
232
+ spyOn(console, 'error');
233
+ function NotAComponent() {
234
+ return [<div />, <div />];
235
+ }
236
+ expect(function() {
237
+ ReactTestUtils.renderIntoDocument(<div><NotAComponent /></div>);
238
+ }).toThrow(); // has no method 'render'
239
+ expect(console.error.calls.length).toBe(1);
240
+ expect(console.error.argsForCall[0][0]).toContain(
241
+ 'Warning: NotAComponent must be a class extending React.Component or be a stateless ' +
242
+ 'function that returns a valid React element.'
243
+ );
244
+ });
245
});