Allow components to render undefined (#21869)
Ricky committed
Jul 13, 2021 at 15:48 UTC
14bac6193a334eda42e727336e8967419f08f5df
10 files changed
+286
-416
packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js
+28
-24
@@ -924,33 +924,37 @@ describe('ReactDOMServerIntegration', () => {
924
);
925
});
926
927
- describe('components that throw errors', function() {
928
- itThrowsWhenRendering(
929
- 'a function returning undefined',
930
- async render => {
931
- const UndefinedComponent = () => undefined;
932
- await render(<UndefinedComponent />, 1);
933
- },
934
- 'UndefinedComponent(...): Nothing was returned from render. ' +
935
- 'This usually means a return statement is missing. Or, to ' +
936
- 'render nothing, return null.',
937
- );
927
+ describe('components that render nullish', function() {
928
+ itRenders('a function returning null', async render => {
929
+ const NullComponent = () => null;
930
+ await render(<NullComponent />);
931
+ });
932
939
- itThrowsWhenRendering(
940
- 'a class returning undefined',
941
- async render => {
942
- class UndefinedComponent extends React.Component {
943
- render() {
944
- return undefined;
945
- }
933
+ itRenders('a class returning null', async render => {
934
+ class NullComponent extends React.Component {
935
+ render() {
936
+ return null;
937
}
947
- await render(<UndefinedComponent />, 1);
948
- },
949
- 'UndefinedComponent(...): Nothing was returned from render. ' +
950
- 'This usually means a return statement is missing. Or, to ' +
951
- 'render nothing, return null.',
952
- );
938
+ }
939
+ await render(<NullComponent />);
940
+ });
941
+
942
+ itRenders('a function returning undefined', async render => {
943
+ const UndefinedComponent = () => undefined;
944
+ await render(<UndefinedComponent />);
945
+ });
946
+
947
+ itRenders('a class returning undefined', async render => {
948
+ class UndefinedComponent extends React.Component {
949
+ render() {
950
+ return undefined;
951
+ }
952
+ }
953
+ await render(<UndefinedComponent />);
954
+ });
955
+ });
956
957
+ describe('components that throw errors', function() {
958
itThrowsWhenRendering(
959
'a function returning an object',
960
async render => {
packages/react-dom/src/__tests__/ReactEmptyComponent-test.js
+246
-228
@@ -45,145 +45,117 @@ describe('ReactEmptyComponent', () => {
45
};
46
});
47
48
- it('should not produce child DOM nodes for null and false', () => {
49
- class Component1 extends React.Component {
50
- render() {
51
- return null;
52
- }
53
- }
54
-
55
- class Component2 extends React.Component {
56
- render() {
57
- return false;
48
+ describe.each([null, undefined])('when %s', nullORUndefined => {
49
+ it('should not throw when rendering', () => {
50
+ class Component extends React.Component {
51
+ render() {
52
+ return nullORUndefined;
53
+ }
54
}
59
- }
60
-
61
- const container1 = document.createElement('div');
62
- ReactDOM.render(<Component1 />, container1);
63
- expect(container1.children.length).toBe(0);
64
-
65
- const container2 = document.createElement('div');
66
- ReactDOM.render(<Component2 />, container2);
67
- expect(container2.children.length).toBe(0);
68
- });
69
-
70
- it('should still throw when rendering to undefined', () => {
71
- class Component extends React.Component {
72
- render() {}
73
- }
74
-
75
- expect(function() {
76
- ReactTestUtils.renderIntoDocument(<Component />);
77
- }).toThrowError(
78
- 'Component(...): Nothing was returned from render. This usually means a return statement is missing. ' +
79
- 'Or, to render nothing, return null.',
80
- );
81
- });
55
83
- it('should be able to switch between rendering null and a normal tag', () => {
84
- const instance1 = (
85
- <TogglingComponent firstComponent={null} secondComponent={'div'} />
86
- );
87
- const instance2 = (
88
- <TogglingComponent firstComponent={'div'} secondComponent={null} />
89
- );
56
+ expect(function() {
57
+ ReactTestUtils.renderIntoDocument(<Component />);
58
+ }).not.toThrowError();
59
+ });
60
91
- ReactTestUtils.renderIntoDocument(instance1);
92
- ReactTestUtils.renderIntoDocument(instance2);
61
+ it('should not produce child DOM nodes for nullish and false', () => {
62
+ class Component1 extends React.Component {
63
+ render() {
64
+ return nullORUndefined;
65
+ }
66
+ }
67
94
- expect(log).toHaveBeenCalledTimes(4);
95
- expect(log).toHaveBeenNthCalledWith(1, null);
96
- expect(log).toHaveBeenNthCalledWith(
97
- 2,
98
- expect.objectContaining({tagName: 'DIV'}),
99
- );
100
- expect(log).toHaveBeenNthCalledWith(
101
- 3,
102
- expect.objectContaining({tagName: 'DIV'}),
103
- );
104
- expect(log).toHaveBeenNthCalledWith(4, null);
105
- });
68
+ class Component2 extends React.Component {
69
+ render() {
70
+ return false;
71
+ }
72
+ }
73
107
- it('should be able to switch in a list of children', () => {
108
- const instance1 = (
109
- <TogglingComponent firstComponent={null} secondComponent={'div'} />
110
- );
74
+ const container1 = document.createElement('div');
75
+ ReactDOM.render(<Component1 />, container1);
76
+ expect(container1.children.length).toBe(0);
77
112
- ReactTestUtils.renderIntoDocument(
113
- <div>
114
- {instance1}
115
- {instance1}
116
- {instance1}
117
- </div>,
118
- );
78
+ const container2 = document.createElement('div');
79
+ ReactDOM.render(<Component2 />, container2);
80
+ expect(container2.children.length).toBe(0);
81
+ });
82
120
- expect(log).toHaveBeenCalledTimes(6);
121
- expect(log).toHaveBeenNthCalledWith(1, null);
122
- expect(log).toHaveBeenNthCalledWith(2, null);
123
- expect(log).toHaveBeenNthCalledWith(3, null);
124
- expect(log).toHaveBeenNthCalledWith(
125
- 4,
126
- expect.objectContaining({tagName: 'DIV'}),
127
- );
128
- expect(log).toHaveBeenNthCalledWith(
129
- 5,
130
- expect.objectContaining({tagName: 'DIV'}),
131
- );
132
- expect(log).toHaveBeenNthCalledWith(
133
- 6,
134
- expect.objectContaining({tagName: 'DIV'}),
135
- );
136
- });
137
-
138
- it('should distinguish between a script placeholder and an actual script tag', () => {
139
- const instance1 = (
140
- <TogglingComponent firstComponent={null} secondComponent={'script'} />
141
- );
142
- const instance2 = (
143
- <TogglingComponent firstComponent={'script'} secondComponent={null} />
144
- );
83
+ it('should be able to switch between rendering nullish and a normal tag', () => {
84
+ const instance1 = (
85
+ <TogglingComponent
86
+ firstComponent={nullORUndefined}
87
+ secondComponent={'div'}
88
+ />
89
+ );
90
+ const instance2 = (
91
+ <TogglingComponent
92
+ firstComponent={'div'}
93
+ secondComponent={nullORUndefined}
94
+ />
95
+ );
96
146
- expect(function() {
97
ReactTestUtils.renderIntoDocument(instance1);
148
- }).not.toThrow();
149
- expect(function() {
98
ReactTestUtils.renderIntoDocument(instance2);
151
- }).not.toThrow();
99
153
- expect(log).toHaveBeenCalledTimes(4);
154
- expect(log).toHaveBeenNthCalledWith(1, null);
155
- expect(log).toHaveBeenNthCalledWith(
156
- 2,
157
- expect.objectContaining({tagName: 'SCRIPT'}),
158
- );
159
- expect(log).toHaveBeenNthCalledWith(
160
- 3,
161
- expect.objectContaining({tagName: 'SCRIPT'}),
162
- );
163
- expect(log).toHaveBeenNthCalledWith(4, null);
164
- });
100
+ expect(log).toHaveBeenCalledTimes(4);
101
+ expect(log).toHaveBeenNthCalledWith(1, null);
102
+ expect(log).toHaveBeenNthCalledWith(
103
+ 2,
104
+ expect.objectContaining({tagName: 'DIV'}),
105
+ );
106
+ expect(log).toHaveBeenNthCalledWith(
107
+ 3,
108
+ expect.objectContaining({tagName: 'DIV'}),
109
+ );
110
+ expect(log).toHaveBeenNthCalledWith(4, null);
111
+ });
112
166
- it(
167
- 'should have findDOMNode return null when multiple layers of composite ' +
168
- 'components render to the same null placeholder',
169
- () => {
170
- class GrandChild extends React.Component {
171
- render() {
172
- return null;
173
- }
174
- }
113
+ it('should be able to switch in a list of children', () => {
114
+ const instance1 = (
115
+ <TogglingComponent
116
+ firstComponent={nullORUndefined}
117
+ secondComponent={'div'}
118
+ />
119
+ );
120
176
- class Child extends React.Component {
177
- render() {
178
- return <GrandChild />;
179
- }
180
- }
121
+ ReactTestUtils.renderIntoDocument(
122
+ <div>
123
+ {instance1}
124
+ {instance1}
125
+ {instance1}
126
+ </div>,
127
+ );
128
+
129
+ expect(log).toHaveBeenCalledTimes(6);
130
+ expect(log).toHaveBeenNthCalledWith(1, null);
131
+ expect(log).toHaveBeenNthCalledWith(2, null);
132
+ expect(log).toHaveBeenNthCalledWith(3, null);
133
+ expect(log).toHaveBeenNthCalledWith(
134
+ 4,
135
+ expect.objectContaining({tagName: 'DIV'}),
136
+ );
137
+ expect(log).toHaveBeenNthCalledWith(
138
+ 5,
139
+ expect.objectContaining({tagName: 'DIV'}),
140
+ );
141
+ expect(log).toHaveBeenNthCalledWith(
142
+ 6,
143
+ expect.objectContaining({tagName: 'DIV'}),
144
+ );
145
+ });
146
147
+ it('should distinguish between a script placeholder and an actual script tag', () => {
148
const instance1 = (
183
- <TogglingComponent firstComponent={'div'} secondComponent={Child} />
149
+ <TogglingComponent
150
+ firstComponent={nullORUndefined}
151
+ secondComponent={'script'}
152
+ />
153
);
154
const instance2 = (
186
- <TogglingComponent firstComponent={Child} secondComponent={'div'} />
155
+ <TogglingComponent
156
+ firstComponent={'script'}
157
+ secondComponent={nullORUndefined}
158
+ />
159
);
160
161
expect(function() {
@@ -194,146 +166,192 @@ describe('ReactEmptyComponent', () => {
166
}).not.toThrow();
167
168
expect(log).toHaveBeenCalledTimes(4);
169
+ expect(log).toHaveBeenNthCalledWith(1, null);
170
expect(log).toHaveBeenNthCalledWith(
198
- 1,
199
- expect.objectContaining({tagName: 'DIV'}),
171
+ 2,
172
+ expect.objectContaining({tagName: 'SCRIPT'}),
173
);
201
- expect(log).toHaveBeenNthCalledWith(2, null);
202
- expect(log).toHaveBeenNthCalledWith(3, null);
174
expect(log).toHaveBeenNthCalledWith(
204
- 4,
205
- expect.objectContaining({tagName: 'DIV'}),
175
+ 3,
176
+ expect.objectContaining({tagName: 'SCRIPT'}),
177
);
207
- },
208
- );
178
+ expect(log).toHaveBeenNthCalledWith(4, null);
179
+ });
180
+
181
+ it(
182
+ 'should have findDOMNode return null when multiple layers of composite ' +
183
+ 'components render to the same nullish placeholder',
184
+ () => {
185
+ class GrandChild extends React.Component {
186
+ render() {
187
+ return nullORUndefined;
188
+ }
189
+ }
190
210
- it('works when switching components', () => {
211
- let assertions = 0;
191
+ class Child extends React.Component {
192
+ render() {
193
+ return <GrandChild />;
194
+ }
195
+ }
196
213
- class Inner extends React.Component {
214
- render() {
215
- return <span />;
216
- }
197
+ const instance1 = (
198
+ <TogglingComponent firstComponent={'div'} secondComponent={Child} />
199
+ );
200
+ const instance2 = (
201
+ <TogglingComponent firstComponent={Child} secondComponent={'div'} />
202
+ );
203
218
- componentDidMount() {
219
- // Make sure the DOM node resolves properly even if we're replacing a
220
- // `null` component
221
- expect(ReactDOM.findDOMNode(this)).not.toBe(null);
222
- assertions++;
223
- }
204
+ expect(function() {
205
+ ReactTestUtils.renderIntoDocument(instance1);
206
+ }).not.toThrow();
207
+ expect(function() {
208
+ ReactTestUtils.renderIntoDocument(instance2);
209
+ }).not.toThrow();
210
+
211
+ expect(log).toHaveBeenCalledTimes(4);
212
+ expect(log).toHaveBeenNthCalledWith(
213
+ 1,
214
+ expect.objectContaining({tagName: 'DIV'}),
215
+ );
216
+ expect(log).toHaveBeenNthCalledWith(2, null);
217
+ expect(log).toHaveBeenNthCalledWith(3, null);
218
+ expect(log).toHaveBeenNthCalledWith(
219
+ 4,
220
+ expect.objectContaining({tagName: 'DIV'}),
221
+ );
222
+ },
223
+ );
224
225
- componentWillUnmount() {
226
- // Even though we're getting replaced by `null`, we haven't been
227
- // replaced yet!
228
- expect(ReactDOM.findDOMNode(this)).not.toBe(null);
229
- assertions++;
225
+ it('works when switching components', () => {
226
+ let assertions = 0;
227
+
228
+ class Inner extends React.Component {
229
+ render() {
230
+ return <span />;
231
+ }
232
+
233
+ componentDidMount() {
234
+ // Make sure the DOM node resolves properly even if we're replacing a
235
+ // `null` component
236
+ expect(ReactDOM.findDOMNode(this)).not.toBe(null);
237
+ assertions++;
238
+ }
239
+
240
+ componentWillUnmount() {
241
+ // Even though we're getting replaced by `null`, we haven't been
242
+ // replaced yet!
243
+ expect(ReactDOM.findDOMNode(this)).not.toBe(null);
244
+ assertions++;
245
+ }
246
}
231
- }
247
233
- class Wrapper extends React.Component {
234
- render() {
235
- return this.props.showInner ? <Inner /> : null;
248
+ class Wrapper extends React.Component {
249
+ render() {
250
+ return this.props.showInner ? <Inner /> : nullORUndefined;
251
+ }
252
}
237
- }
253
239
- const el = document.createElement('div');
240
- let component;
254
+ const el = document.createElement('div');
255
+ let component;
256
242
- // Render the <Inner /> component...
243
- component = ReactDOM.render(<Wrapper showInner={true} />, el);
244
- expect(ReactDOM.findDOMNode(component)).not.toBe(null);
257
+ // Render the <Inner /> component...
258
+ component = ReactDOM.render(<Wrapper showInner={true} />, el);
259
+ expect(ReactDOM.findDOMNode(component)).not.toBe(null);
260
246
- // Switch to null...
247
- component = ReactDOM.render(<Wrapper showInner={false} />, el);
248
- expect(ReactDOM.findDOMNode(component)).toBe(null);
261
+ // Switch to null...
262
+ component = ReactDOM.render(<Wrapper showInner={false} />, el);
263
+ expect(ReactDOM.findDOMNode(component)).toBe(null);
264
250
- // ...then switch back.
251
- component = ReactDOM.render(<Wrapper showInner={true} />, el);
252
- expect(ReactDOM.findDOMNode(component)).not.toBe(null);
265
+ // ...then switch back.
266
+ component = ReactDOM.render(<Wrapper showInner={true} />, el);
267
+ expect(ReactDOM.findDOMNode(component)).not.toBe(null);
268
254
- expect(assertions).toBe(3);
255
- });
269
+ expect(assertions).toBe(3);
270
+ });
271
257
- it('can render null at the top level', () => {
258
- const div = document.createElement('div');
259
- ReactDOM.render(null, div);
260
- expect(div.innerHTML).toBe('');
261
- });
272
+ it('can render nullish at the top level', () => {
273
+ const div = document.createElement('div');
274
+ ReactDOM.render(nullORUndefined, div);
275
+ expect(div.innerHTML).toBe('');
276
+ });
277
263
- it('does not break when updating during mount', () => {
264
- class Child extends React.Component {
265
- componentDidMount() {
266
- if (this.props.onMount) {
267
- this.props.onMount();
278
+ it('does not break when updating during mount', () => {
279
+ class Child extends React.Component {
280
+ componentDidMount() {
281
+ if (this.props.onMount) {
282
+ this.props.onMount();
283
+ }
284
}
269
- }
285
271
- render() {
272
- if (!this.props.visible) {
273
- return null;
274
- }
286
+ render() {
287
+ if (!this.props.visible) {
288
+ return nullORUndefined;
289
+ }
290
276
- return <div>hello world</div>;
291
+ return <div>hello world</div>;
292
+ }
293
}
278
- }
294
280
- class Parent extends React.Component {
281
- update = () => {
282
- this.forceUpdate();
283
- };
295
+ class Parent extends React.Component {
296
+ update = () => {
297
+ this.forceUpdate();
298
+ };
299
285
- render() {
286
- return (
287
- <div>
288
- <Child key="1" visible={false} />
289
- <Child key="0" visible={true} onMount={this.update} />
290
- <Child key="2" visible={false} />
291
- </div>
292
- );
300
+ render() {
301
+ return (
302
+ <div>
303
+ <Child key="1" visible={false} />
304
+ <Child key="0" visible={true} onMount={this.update} />
305
+ <Child key="2" visible={false} />
306
+ </div>
307
+ );
308
+ }
309
}
294
- }
310
296
- expect(function() {
297
- ReactTestUtils.renderIntoDocument(<Parent />);
298
- }).not.toThrow();
299
- });
311
+ expect(function() {
312
+ ReactTestUtils.renderIntoDocument(<Parent />);
313
+ }).not.toThrow();
314
+ });
315
301
- it('preserves the dom node during updates', () => {
302
- class Empty extends React.Component {
303
- render() {
304
- return null;
316
+ it('preserves the dom node during updates', () => {
317
+ class Empty extends React.Component {
318
+ render() {
319
+ return nullORUndefined;
320
+ }
321
}
306
- }
322
308
- const container = document.createElement('div');
323
+ const container = document.createElement('div');
324
310
- ReactDOM.render(<Empty />, container);
311
- const noscript1 = container.firstChild;
312
- expect(noscript1).toBe(null);
325
+ ReactDOM.render(<Empty />, container);
326
+ const noscript1 = container.firstChild;
327
+ expect(noscript1).toBe(null);
328
314
- // This update shouldn't create a DOM node
315
- ReactDOM.render(<Empty />, container);
316
- const noscript2 = container.firstChild;
317
- expect(noscript2).toBe(null);
318
- });
329
+ // This update shouldn't create a DOM node
330
+ ReactDOM.render(<Empty />, container);
331
+ const noscript2 = container.firstChild;
332
+ expect(noscript2).toBe(null);
333
+ });
334
320
- it('should warn about React.forwardRef that returns undefined', () => {
321
- const Empty = () => {};
322
- const EmptyForwardRef = React.forwardRef(Empty);
335
+ it('should not warn about React.forwardRef that returns nullish', () => {
336
+ const Empty = () => {
337
+ return nullORUndefined;
338
+ };
339
+ const EmptyForwardRef = React.forwardRef(Empty);
340
324
- expect(() => {
325
- ReactTestUtils.renderIntoDocument(<EmptyForwardRef />);
326
- }).toThrowError(
327
- 'ForwardRef(Empty)(...): Nothing was returned from render.',
328
- );
329
- });
341
+ expect(() => {
342
+ ReactTestUtils.renderIntoDocument(<EmptyForwardRef />);
343
+ }).not.toThrowError();
344
+ });
345
331
- it('should warn about React.memo that returns undefined', () => {
332
- const Empty = () => {};
333
- const EmptyMemo = React.memo(Empty);
346
+ it('should not warn about React.memo that returns nullish', () => {
347
+ const Empty = () => {
348
+ return nullORUndefined;
349
+ };
350
+ const EmptyMemo = React.memo(Empty);
351
335
- expect(() => {
336
- ReactTestUtils.renderIntoDocument(<EmptyMemo />);
337
- }).toThrowError('Empty(...): Nothing was returned from render.');
352
+ expect(() => {
353
+ ReactTestUtils.renderIntoDocument(<EmptyMemo />);
354
+ }).not.toThrowError();
355
+ });
356
});
357
});
packages/react-dom/src/__tests__/ReactFunctionComponent-test.js
+2
-5
@@ -136,7 +136,7 @@ describe('ReactFunctionComponent', () => {
136
);
137
});
138
139
- it('should throw when stateless component returns undefined', () => {
139
+ it('should not throw when stateless component returns undefined', () => {
140
function NotAComponent() {}
141
expect(function() {
142
ReactTestUtils.renderIntoDocument(
@@ -144,10 +144,7 @@ describe('ReactFunctionComponent', () => {
144
<NotAComponent />
145
</div>,
146
);
147
- }).toThrowError(
148
- 'NotAComponent(...): Nothing was returned from render. ' +
149
- 'This usually means a return statement is missing. Or, to render nothing, return null.',
150
- );
147
+ }).not.toThrowError();
148
});
149
150
it('should throw on string refs in pure functions', () => {
packages/react-dom/src/__tests__/ReactMockedComponent-test.js
+5
-29
@@ -30,42 +30,18 @@ describe('ReactMockedComponent', () => {
30
MockedComponent.prototype.render = jest.fn();
31
});
32
33
- it('should allow a mocked component to be rendered in dev', () => {
33
+ it('should allow a mocked component to be rendered', () => {
34
const container = document.createElement('container');
35
- if (__DEV__) {
36
- ReactDOM.render(<MockedComponent />, container);
37
- } else {
38
- expect(() => ReactDOM.render(<MockedComponent />, container)).toThrow(
39
- 'Nothing was returned from render.',
40
- );
41
- }
35
+ ReactDOM.render(<MockedComponent />, container);
36
});
37
38
it('should allow a mocked component to be updated in dev', () => {
39
const container = document.createElement('container');
46
- if (__DEV__) {
47
- ReactDOM.render(<MockedComponent />, container);
48
- } else {
49
- expect(() => ReactDOM.render(<MockedComponent />, container)).toThrow(
50
- 'Nothing was returned from render.',
51
- );
52
- }
53
- if (__DEV__) {
54
- ReactDOM.render(<MockedComponent />, container);
55
- } else {
56
- expect(() => ReactDOM.render(<MockedComponent />, container)).toThrow(
57
- 'Nothing was returned from render.',
58
- );
59
- }
40
+ ReactDOM.render(<MockedComponent />, container);
41
+ ReactDOM.render(<MockedComponent />, container);
42
});
43
44
it('should allow a mocked component to be rendered in dev (SSR)', () => {
63
- if (__DEV__) {
64
- ReactDOMServer.renderToString(<MockedComponent />);
65
- } else {
66
- expect(() => ReactDOMServer.renderToString(<MockedComponent />)).toThrow(
67
- 'Nothing was returned from render.',
68
- );
69
- }
45
+ ReactDOMServer.renderToString(<MockedComponent />);
46
});
47
});
packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js
+1
-1
@@ -242,7 +242,7 @@ module.exports = function(initModules) {
242
await asyncReactDOMRender(element, cleanContainer, true);
243
// This gives us the expected text content.
244
const cleanTextContent =
245
- cleanContainer.lastChild && cleanContainer.lastChild.textContent;
245
+ (cleanContainer.lastChild && cleanContainer.lastChild.textContent) || '';
246
247
// The only guarantee is that text content has been patched up if needed.
248
expect(hydratedTextContent).toBe(cleanTextContent);
packages/react-dom/src/server/ReactPartialRenderer.js
-22
@@ -391,18 +391,6 @@ function createOpenTagMarkup(
391
return ret;
392
}
393
394
-function validateRenderResult(child, type) {
395
- if (child === undefined) {
396
- invariant(
397
- false,
398
- '%s(...): Nothing was returned from render. This usually means a ' +
399
- 'return statement is missing. Or, to render nothing, ' +
400
- 'return null.',
401
- getComponentNameFromType(type) || 'Component',
402
- );
403
- }
404
-}
405
-
394
function resolve(
395
child: mixed,
396
context: Object,
@@ -631,7 +619,6 @@ function resolve(
619
inst.render == null
620
) {
621
child = inst;
634
- validateRenderResult(child, Component);
622
return;
623
}
624
}
@@ -720,15 +707,6 @@ function resolve(
707
}
708
child = inst.render();
709
723
- if (__DEV__) {
724
- if (child === undefined && inst.render._isMockFunction) {
725
- // This is probably bad practice. Consider warning here and
726
- // deprecating this convenience.
727
- child = null;
728
- }
729
- }
730
- validateRenderResult(child, Component);
731
-
710
let childContext;
711
if (disableLegacyContext) {
712
if (__DEV__) {
packages/react-reconciler/src/ReactChildFiber.new.js
+1
-39
@@ -21,15 +21,7 @@ import {
21
REACT_PORTAL_TYPE,
22
REACT_LAZY_TYPE,
23
} from 'shared/ReactSymbols';
24
-import {
25
- FunctionComponent,
26
- ClassComponent,
27
- HostText,
28
- HostPortal,
29
- ForwardRef,
30
- Fragment,
31
- SimpleMemoComponent,
32
-} from './ReactWorkTags';
24
+import {ClassComponent, HostText, HostPortal, Fragment} from './ReactWorkTags';
25
import invariant from 'shared/invariant';
26
import isArray from 'shared/isArray';
27
import {
@@ -1302,36 +1294,6 @@ function ChildReconciler(shouldTrackSideEffects) {
1294
warnOnFunctionType(returnFiber);
1295
}
1296
}
1305
- if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
1306
- // If the new child is undefined, and the return fiber is a composite
1307
- // component, throw an error. If Fiber return types are disabled,
1308
- // we already threw above.
1309
- switch (returnFiber.tag) {
1310
- case ClassComponent: {
1311
- if (__DEV__) {
1312
- const instance = returnFiber.stateNode;
1313
- if (instance.render._isMockFunction) {
1314
- // We allow auto-mocks to proceed as if they're returning null.
1315
- break;
1316
- }
1317
- }
1318
- }
1319
- // Intentionally fall through to the next case, which handles both
1320
- // functions and classes
1321
- // eslint-disable-next-lined no-fallthrough
1322
- case FunctionComponent:
1323
- case ForwardRef:
1324
- case SimpleMemoComponent: {
1325
- invariant(
1326
- false,
1327
- '%s(...): Nothing was returned from render. This usually means a ' +
1328
- 'return statement is missing. Or, to render nothing, ' +
1329
- 'return null.',
1330
- getComponentNameFromFiber(returnFiber) || 'Component',
1331
- );
1332
- }
1333
- }
1334
- }
1297
1298
// Remaining cases are all treated as empty.
1299
return deleteRemainingChildren(returnFiber, currentFirstChild);
packages/react-reconciler/src/ReactChildFiber.old.js
+1
-39
@@ -21,15 +21,7 @@ import {
21
REACT_PORTAL_TYPE,
22
REACT_LAZY_TYPE,
23
} from 'shared/ReactSymbols';
24
-import {
25
- FunctionComponent,
26
- ClassComponent,
27
- HostText,
28
- HostPortal,
29
- ForwardRef,
30
- Fragment,
31
- SimpleMemoComponent,
32
-} from './ReactWorkTags';
24
+import {ClassComponent, HostText, HostPortal, Fragment} from './ReactWorkTags';
25
import invariant from 'shared/invariant';
26
import isArray from 'shared/isArray';
27
import {
@@ -1302,36 +1294,6 @@ function ChildReconciler(shouldTrackSideEffects) {
1294
warnOnFunctionType(returnFiber);
1295
}
1296
}
1305
- if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
1306
- // If the new child is undefined, and the return fiber is a composite
1307
- // component, throw an error. If Fiber return types are disabled,
1308
- // we already threw above.
1309
- switch (returnFiber.tag) {
1310
- case ClassComponent: {
1311
- if (__DEV__) {
1312
- const instance = returnFiber.stateNode;
1313
- if (instance.render._isMockFunction) {
1314
- // We allow auto-mocks to proceed as if they're returning null.
1315
- break;
1316
- }
1317
- }
1318
- }
1319
- // Intentionally fall through to the next case, which handles both
1320
- // functions and classes
1321
- // eslint-disable-next-lined no-fallthrough
1322
- case FunctionComponent:
1323
- case ForwardRef:
1324
- case SimpleMemoComponent: {
1325
- invariant(
1326
- false,
1327
- '%s(...): Nothing was returned from render. This usually means a ' +
1328
- 'return statement is missing. Or, to render nothing, ' +
1329
- 'return null.',
1330
- getComponentNameFromFiber(returnFiber) || 'Component',
1331
- );
1332
- }
1333
- }
1334
- }
1297
1298
// Remaining cases are all treated as empty.
1299
return deleteRemainingChildren(returnFiber, currentFirstChild);
packages/react-server/src/ReactFizzServer.js
+1
-22
@@ -554,16 +554,6 @@ function shouldConstruct(Component) {
554
return Component.prototype && Component.prototype.isReactComponent;
555
}
556
557
-function invalidRenderResult(type: any): void {
558
- invariant(
559
- false,
560
- '%s(...): Nothing was returned from render. This usually means a ' +
561
- 'return statement is missing. Or, to render nothing, ' +
562
- 'return null.',
563
- getComponentNameFromType(type) || 'Component',
564
- );
565
-}
566
-
557
function renderWithHooks<Props, SecondArg>(
558
request: Request,
559
task: Task,
@@ -574,11 +564,7 @@ function renderWithHooks<Props, SecondArg>(
564
const componentIdentity = {};
565
prepareToUseHooks(componentIdentity);
566
const result = Component(props, secondArg);
577
- const children = finishHooks(Component, props, result, secondArg);
578
- if (children === undefined) {
579
- invalidRenderResult(Component);
580
- }
581
- return children;
567
+ return finishHooks(Component, props, result, secondArg);
568
}
569
570
function finishClassComponent(
@@ -589,13 +575,6 @@ function finishClassComponent(
575
props: any,
576
): ReactNodeList {
577
const nextChildren = instance.render();
592
- if (nextChildren === undefined) {
593
- if (__DEV__ && instance.render._isMockFunction) {
594
- // We allow auto-mocks to proceed as if they're returning null.
595
- } else {
596
- invalidRenderResult(Component);
597
- }
598
- }
578
579
if (__DEV__) {
580
if (instance.props !== props) {
packages/shared/__tests__/ReactError-test.internal.js
+1
-7
@@ -46,18 +46,12 @@ describe('ReactError', () => {
46
);
47
});
48
49
- // @gate build === "production"
49
it('should serialize arguments', () => {
50
function Oops() {
51
return;
52
}
53
Oops.displayName = '#wtf';
54
const container = document.createElement('div');
56
- expect(() => ReactDOM.render(<Oops />, container)).toThrowError(
57
- 'Minified React error #152; visit ' +
58
- 'https://reactjs.org/docs/error-decoder.html?invariant=152&args[]=%23wtf' +
59
- ' for the full message or use the non-minified dev environment' +
60
- ' for full errors and additional helpful warnings.',
61
- );
55
+ expect(() => ReactDOM.render(<Oops />, container)).not.toThrowError();
56
});
57
});