From 14bac6193a334eda42e727336e8967419f08f5df Mon Sep 17 00:00:00 2001 From: Ricky Date: Tue, 13 Jul 2021 15:48:11 -0400 Subject: [PATCH] Allow components to render undefined (#21869) --- .../ReactDOMServerIntegrationElements-test.js | 52 +- .../src/__tests__/ReactEmptyComponent-test.js | 528 +++++++++--------- .../__tests__/ReactFunctionComponent-test.js | 7 +- .../__tests__/ReactMockedComponent-test.js | 34 +- .../ReactDOMServerIntegrationTestUtils.js | 2 +- .../src/server/ReactPartialRenderer.js | 22 - .../src/ReactChildFiber.new.js | 40 +- .../src/ReactChildFiber.old.js | 40 +- packages/react-server/src/ReactFizzServer.js | 23 +- .../__tests__/ReactError-test.internal.js | 8 +- 10 files changed, 313 insertions(+), 443 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js index 859647b39c..c41170aa54 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js @@ -924,33 +924,37 @@ describe('ReactDOMServerIntegration', () => { ); }); - describe('components that throw errors', function() { - itThrowsWhenRendering( - 'a function returning undefined', - async render => { - const UndefinedComponent = () => undefined; - await render(, 1); - }, - 'UndefinedComponent(...): Nothing was returned from render. ' + - 'This usually means a return statement is missing. Or, to ' + - 'render nothing, return null.', - ); + describe('components that render nullish', function() { + itRenders('a function returning null', async render => { + const NullComponent = () => null; + await render(); + }); - itThrowsWhenRendering( - 'a class returning undefined', - async render => { - class UndefinedComponent extends React.Component { - render() { - return undefined; - } + itRenders('a class returning null', async render => { + class NullComponent extends React.Component { + render() { + return null; } - await render(, 1); - }, - 'UndefinedComponent(...): Nothing was returned from render. ' + - 'This usually means a return statement is missing. Or, to ' + - 'render nothing, return null.', - ); + } + await render(); + }); + itRenders('a function returning undefined', async render => { + const UndefinedComponent = () => undefined; + await render(); + }); + + itRenders('a class returning undefined', async render => { + class UndefinedComponent extends React.Component { + render() { + return undefined; + } + } + await render(); + }); + }); + + describe('components that throw errors', function() { itThrowsWhenRendering( 'a function returning an object', async render => { diff --git a/packages/react-dom/src/__tests__/ReactEmptyComponent-test.js b/packages/react-dom/src/__tests__/ReactEmptyComponent-test.js index 0c0b1d1a92..12146e9b7e 100644 --- a/packages/react-dom/src/__tests__/ReactEmptyComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactEmptyComponent-test.js @@ -45,145 +45,117 @@ describe('ReactEmptyComponent', () => { }; }); - it('should not produce child DOM nodes for null and false', () => { - class Component1 extends React.Component { - render() { - return null; - } - } - - class Component2 extends React.Component { - render() { - return false; - } - } - - const container1 = document.createElement('div'); - ReactDOM.render(, container1); - expect(container1.children.length).toBe(0); - - const container2 = document.createElement('div'); - ReactDOM.render(, container2); - expect(container2.children.length).toBe(0); - }); - - it('should still throw when rendering to undefined', () => { - class Component extends React.Component { - render() {} - } - - expect(function() { - ReactTestUtils.renderIntoDocument(); - }).toThrowError( - 'Component(...): Nothing was returned from render. This usually means a return statement is missing. ' + - 'Or, to render nothing, return null.', - ); - }); - - it('should be able to switch between rendering null and a normal tag', () => { - const instance1 = ( - - ); - const instance2 = ( - - ); - - ReactTestUtils.renderIntoDocument(instance1); - ReactTestUtils.renderIntoDocument(instance2); - - expect(log).toHaveBeenCalledTimes(4); - expect(log).toHaveBeenNthCalledWith(1, null); - expect(log).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({tagName: 'DIV'}), - ); - expect(log).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({tagName: 'DIV'}), - ); - expect(log).toHaveBeenNthCalledWith(4, null); - }); - - it('should be able to switch in a list of children', () => { - const instance1 = ( - - ); - - ReactTestUtils.renderIntoDocument( -
- {instance1} - {instance1} - {instance1} -
, - ); - - expect(log).toHaveBeenCalledTimes(6); - expect(log).toHaveBeenNthCalledWith(1, null); - expect(log).toHaveBeenNthCalledWith(2, null); - expect(log).toHaveBeenNthCalledWith(3, null); - expect(log).toHaveBeenNthCalledWith( - 4, - expect.objectContaining({tagName: 'DIV'}), - ); - expect(log).toHaveBeenNthCalledWith( - 5, - expect.objectContaining({tagName: 'DIV'}), - ); - expect(log).toHaveBeenNthCalledWith( - 6, - expect.objectContaining({tagName: 'DIV'}), - ); - }); - - it('should distinguish between a script placeholder and an actual script tag', () => { - const instance1 = ( - - ); - const instance2 = ( - - ); - - expect(function() { - ReactTestUtils.renderIntoDocument(instance1); - }).not.toThrow(); - expect(function() { - ReactTestUtils.renderIntoDocument(instance2); - }).not.toThrow(); - - expect(log).toHaveBeenCalledTimes(4); - expect(log).toHaveBeenNthCalledWith(1, null); - expect(log).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({tagName: 'SCRIPT'}), - ); - expect(log).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({tagName: 'SCRIPT'}), - ); - expect(log).toHaveBeenNthCalledWith(4, null); - }); - - it( - 'should have findDOMNode return null when multiple layers of composite ' + - 'components render to the same null placeholder', - () => { - class GrandChild extends React.Component { + describe.each([null, undefined])('when %s', nullORUndefined => { + it('should not throw when rendering', () => { + class Component extends React.Component { render() { - return null; + return nullORUndefined; } } - class Child extends React.Component { + expect(function() { + ReactTestUtils.renderIntoDocument(); + }).not.toThrowError(); + }); + + it('should not produce child DOM nodes for nullish and false', () => { + class Component1 extends React.Component { render() { - return ; + return nullORUndefined; } } + class Component2 extends React.Component { + render() { + return false; + } + } + + const container1 = document.createElement('div'); + ReactDOM.render(, container1); + expect(container1.children.length).toBe(0); + + const container2 = document.createElement('div'); + ReactDOM.render(, container2); + expect(container2.children.length).toBe(0); + }); + + it('should be able to switch between rendering nullish and a normal tag', () => { const instance1 = ( - + ); const instance2 = ( - + + ); + + ReactTestUtils.renderIntoDocument(instance1); + ReactTestUtils.renderIntoDocument(instance2); + + expect(log).toHaveBeenCalledTimes(4); + expect(log).toHaveBeenNthCalledWith(1, null); + expect(log).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({tagName: 'DIV'}), + ); + expect(log).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({tagName: 'DIV'}), + ); + expect(log).toHaveBeenNthCalledWith(4, null); + }); + + it('should be able to switch in a list of children', () => { + const instance1 = ( + + ); + + ReactTestUtils.renderIntoDocument( +
+ {instance1} + {instance1} + {instance1} +
, + ); + + expect(log).toHaveBeenCalledTimes(6); + expect(log).toHaveBeenNthCalledWith(1, null); + expect(log).toHaveBeenNthCalledWith(2, null); + expect(log).toHaveBeenNthCalledWith(3, null); + expect(log).toHaveBeenNthCalledWith( + 4, + expect.objectContaining({tagName: 'DIV'}), + ); + expect(log).toHaveBeenNthCalledWith( + 5, + expect.objectContaining({tagName: 'DIV'}), + ); + expect(log).toHaveBeenNthCalledWith( + 6, + expect.objectContaining({tagName: 'DIV'}), + ); + }); + + it('should distinguish between a script placeholder and an actual script tag', () => { + const instance1 = ( + + ); + const instance2 = ( + ); expect(function() { @@ -194,146 +166,192 @@ describe('ReactEmptyComponent', () => { }).not.toThrow(); expect(log).toHaveBeenCalledTimes(4); + expect(log).toHaveBeenNthCalledWith(1, null); expect(log).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({tagName: 'DIV'}), + 2, + expect.objectContaining({tagName: 'SCRIPT'}), ); - expect(log).toHaveBeenNthCalledWith(2, null); - expect(log).toHaveBeenNthCalledWith(3, null); expect(log).toHaveBeenNthCalledWith( - 4, - expect.objectContaining({tagName: 'DIV'}), + 3, + expect.objectContaining({tagName: 'SCRIPT'}), ); - }, - ); + expect(log).toHaveBeenNthCalledWith(4, null); + }); - it('works when switching components', () => { - let assertions = 0; - - class Inner extends React.Component { - render() { - return ; - } - - componentDidMount() { - // Make sure the DOM node resolves properly even if we're replacing a - // `null` component - expect(ReactDOM.findDOMNode(this)).not.toBe(null); - assertions++; - } - - componentWillUnmount() { - // Even though we're getting replaced by `null`, we haven't been - // replaced yet! - expect(ReactDOM.findDOMNode(this)).not.toBe(null); - assertions++; - } - } - - class Wrapper extends React.Component { - render() { - return this.props.showInner ? : null; - } - } - - const el = document.createElement('div'); - let component; - - // Render the component... - component = ReactDOM.render(, el); - expect(ReactDOM.findDOMNode(component)).not.toBe(null); - - // Switch to null... - component = ReactDOM.render(, el); - expect(ReactDOM.findDOMNode(component)).toBe(null); - - // ...then switch back. - component = ReactDOM.render(, el); - expect(ReactDOM.findDOMNode(component)).not.toBe(null); - - expect(assertions).toBe(3); - }); - - it('can render null at the top level', () => { - const div = document.createElement('div'); - ReactDOM.render(null, div); - expect(div.innerHTML).toBe(''); - }); - - it('does not break when updating during mount', () => { - class Child extends React.Component { - componentDidMount() { - if (this.props.onMount) { - this.props.onMount(); - } - } - - render() { - if (!this.props.visible) { - return null; + it( + 'should have findDOMNode return null when multiple layers of composite ' + + 'components render to the same nullish placeholder', + () => { + class GrandChild extends React.Component { + render() { + return nullORUndefined; + } } - return
hello world
; - } - } + class Child extends React.Component { + render() { + return ; + } + } - class Parent extends React.Component { - update = () => { - this.forceUpdate(); - }; - - render() { - return ( -
- - - -
+ const instance1 = ( + + ); + const instance2 = ( + ); - } - } - expect(function() { - ReactTestUtils.renderIntoDocument(); - }).not.toThrow(); - }); + expect(function() { + ReactTestUtils.renderIntoDocument(instance1); + }).not.toThrow(); + expect(function() { + ReactTestUtils.renderIntoDocument(instance2); + }).not.toThrow(); - it('preserves the dom node during updates', () => { - class Empty extends React.Component { - render() { - return null; - } - } - - const container = document.createElement('div'); - - ReactDOM.render(, container); - const noscript1 = container.firstChild; - expect(noscript1).toBe(null); - - // This update shouldn't create a DOM node - ReactDOM.render(, container); - const noscript2 = container.firstChild; - expect(noscript2).toBe(null); - }); - - it('should warn about React.forwardRef that returns undefined', () => { - const Empty = () => {}; - const EmptyForwardRef = React.forwardRef(Empty); - - expect(() => { - ReactTestUtils.renderIntoDocument(); - }).toThrowError( - 'ForwardRef(Empty)(...): Nothing was returned from render.', + expect(log).toHaveBeenCalledTimes(4); + expect(log).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({tagName: 'DIV'}), + ); + expect(log).toHaveBeenNthCalledWith(2, null); + expect(log).toHaveBeenNthCalledWith(3, null); + expect(log).toHaveBeenNthCalledWith( + 4, + expect.objectContaining({tagName: 'DIV'}), + ); + }, ); - }); - it('should warn about React.memo that returns undefined', () => { - const Empty = () => {}; - const EmptyMemo = React.memo(Empty); + it('works when switching components', () => { + let assertions = 0; - expect(() => { - ReactTestUtils.renderIntoDocument(); - }).toThrowError('Empty(...): Nothing was returned from render.'); + class Inner extends React.Component { + render() { + return ; + } + + componentDidMount() { + // Make sure the DOM node resolves properly even if we're replacing a + // `null` component + expect(ReactDOM.findDOMNode(this)).not.toBe(null); + assertions++; + } + + componentWillUnmount() { + // Even though we're getting replaced by `null`, we haven't been + // replaced yet! + expect(ReactDOM.findDOMNode(this)).not.toBe(null); + assertions++; + } + } + + class Wrapper extends React.Component { + render() { + return this.props.showInner ? : nullORUndefined; + } + } + + const el = document.createElement('div'); + let component; + + // Render the component... + component = ReactDOM.render(, el); + expect(ReactDOM.findDOMNode(component)).not.toBe(null); + + // Switch to null... + component = ReactDOM.render(, el); + expect(ReactDOM.findDOMNode(component)).toBe(null); + + // ...then switch back. + component = ReactDOM.render(, el); + expect(ReactDOM.findDOMNode(component)).not.toBe(null); + + expect(assertions).toBe(3); + }); + + it('can render nullish at the top level', () => { + const div = document.createElement('div'); + ReactDOM.render(nullORUndefined, div); + expect(div.innerHTML).toBe(''); + }); + + it('does not break when updating during mount', () => { + class Child extends React.Component { + componentDidMount() { + if (this.props.onMount) { + this.props.onMount(); + } + } + + render() { + if (!this.props.visible) { + return nullORUndefined; + } + + return
hello world
; + } + } + + class Parent extends React.Component { + update = () => { + this.forceUpdate(); + }; + + render() { + return ( +
+ + + +
+ ); + } + } + + expect(function() { + ReactTestUtils.renderIntoDocument(); + }).not.toThrow(); + }); + + it('preserves the dom node during updates', () => { + class Empty extends React.Component { + render() { + return nullORUndefined; + } + } + + const container = document.createElement('div'); + + ReactDOM.render(, container); + const noscript1 = container.firstChild; + expect(noscript1).toBe(null); + + // This update shouldn't create a DOM node + ReactDOM.render(, container); + const noscript2 = container.firstChild; + expect(noscript2).toBe(null); + }); + + it('should not warn about React.forwardRef that returns nullish', () => { + const Empty = () => { + return nullORUndefined; + }; + const EmptyForwardRef = React.forwardRef(Empty); + + expect(() => { + ReactTestUtils.renderIntoDocument(); + }).not.toThrowError(); + }); + + it('should not warn about React.memo that returns nullish', () => { + const Empty = () => { + return nullORUndefined; + }; + const EmptyMemo = React.memo(Empty); + + expect(() => { + ReactTestUtils.renderIntoDocument(); + }).not.toThrowError(); + }); }); }); diff --git a/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js b/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js index 0aa9aa6928..9228316f88 100644 --- a/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js @@ -136,7 +136,7 @@ describe('ReactFunctionComponent', () => { ); }); - it('should throw when stateless component returns undefined', () => { + it('should not throw when stateless component returns undefined', () => { function NotAComponent() {} expect(function() { ReactTestUtils.renderIntoDocument( @@ -144,10 +144,7 @@ describe('ReactFunctionComponent', () => { , ); - }).toThrowError( - 'NotAComponent(...): Nothing was returned from render. ' + - 'This usually means a return statement is missing. Or, to render nothing, return null.', - ); + }).not.toThrowError(); }); it('should throw on string refs in pure functions', () => { diff --git a/packages/react-dom/src/__tests__/ReactMockedComponent-test.js b/packages/react-dom/src/__tests__/ReactMockedComponent-test.js index c572c41197..9d33a08ca9 100644 --- a/packages/react-dom/src/__tests__/ReactMockedComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactMockedComponent-test.js @@ -30,42 +30,18 @@ describe('ReactMockedComponent', () => { MockedComponent.prototype.render = jest.fn(); }); - it('should allow a mocked component to be rendered in dev', () => { + it('should allow a mocked component to be rendered', () => { const container = document.createElement('container'); - if (__DEV__) { - ReactDOM.render(, container); - } else { - expect(() => ReactDOM.render(, container)).toThrow( - 'Nothing was returned from render.', - ); - } + ReactDOM.render(, container); }); it('should allow a mocked component to be updated in dev', () => { const container = document.createElement('container'); - if (__DEV__) { - ReactDOM.render(, container); - } else { - expect(() => ReactDOM.render(, container)).toThrow( - 'Nothing was returned from render.', - ); - } - if (__DEV__) { - ReactDOM.render(, container); - } else { - expect(() => ReactDOM.render(, container)).toThrow( - 'Nothing was returned from render.', - ); - } + ReactDOM.render(, container); + ReactDOM.render(, container); }); it('should allow a mocked component to be rendered in dev (SSR)', () => { - if (__DEV__) { - ReactDOMServer.renderToString(); - } else { - expect(() => ReactDOMServer.renderToString()).toThrow( - 'Nothing was returned from render.', - ); - } + ReactDOMServer.renderToString(); }); }); diff --git a/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js b/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js index 81be4cb30d..8714059bb3 100644 --- a/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js +++ b/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js @@ -242,7 +242,7 @@ module.exports = function(initModules) { await asyncReactDOMRender(element, cleanContainer, true); // This gives us the expected text content. const cleanTextContent = - cleanContainer.lastChild && cleanContainer.lastChild.textContent; + (cleanContainer.lastChild && cleanContainer.lastChild.textContent) || ''; // The only guarantee is that text content has been patched up if needed. expect(hydratedTextContent).toBe(cleanTextContent); diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index 455309db79..caf1ee2f6f 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -391,18 +391,6 @@ function createOpenTagMarkup( return ret; } -function validateRenderResult(child, type) { - if (child === undefined) { - invariant( - false, - '%s(...): Nothing was returned from render. This usually means a ' + - 'return statement is missing. Or, to render nothing, ' + - 'return null.', - getComponentNameFromType(type) || 'Component', - ); - } -} - function resolve( child: mixed, context: Object, @@ -631,7 +619,6 @@ function resolve( inst.render == null ) { child = inst; - validateRenderResult(child, Component); return; } } @@ -720,15 +707,6 @@ function resolve( } child = inst.render(); - if (__DEV__) { - if (child === undefined && inst.render._isMockFunction) { - // This is probably bad practice. Consider warning here and - // deprecating this convenience. - child = null; - } - } - validateRenderResult(child, Component); - let childContext; if (disableLegacyContext) { if (__DEV__) { diff --git a/packages/react-reconciler/src/ReactChildFiber.new.js b/packages/react-reconciler/src/ReactChildFiber.new.js index 024a903dbd..e3f41115f5 100644 --- a/packages/react-reconciler/src/ReactChildFiber.new.js +++ b/packages/react-reconciler/src/ReactChildFiber.new.js @@ -21,15 +21,7 @@ import { REACT_PORTAL_TYPE, REACT_LAZY_TYPE, } from 'shared/ReactSymbols'; -import { - FunctionComponent, - ClassComponent, - HostText, - HostPortal, - ForwardRef, - Fragment, - SimpleMemoComponent, -} from './ReactWorkTags'; +import {ClassComponent, HostText, HostPortal, Fragment} from './ReactWorkTags'; import invariant from 'shared/invariant'; import isArray from 'shared/isArray'; import { @@ -1302,36 +1294,6 @@ function ChildReconciler(shouldTrackSideEffects) { warnOnFunctionType(returnFiber); } } - if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) { - // If the new child is undefined, and the return fiber is a composite - // component, throw an error. If Fiber return types are disabled, - // we already threw above. - switch (returnFiber.tag) { - case ClassComponent: { - if (__DEV__) { - const instance = returnFiber.stateNode; - if (instance.render._isMockFunction) { - // We allow auto-mocks to proceed as if they're returning null. - break; - } - } - } - // Intentionally fall through to the next case, which handles both - // functions and classes - // eslint-disable-next-lined no-fallthrough - case FunctionComponent: - case ForwardRef: - case SimpleMemoComponent: { - invariant( - false, - '%s(...): Nothing was returned from render. This usually means a ' + - 'return statement is missing. Or, to render nothing, ' + - 'return null.', - getComponentNameFromFiber(returnFiber) || 'Component', - ); - } - } - } // Remaining cases are all treated as empty. return deleteRemainingChildren(returnFiber, currentFirstChild); diff --git a/packages/react-reconciler/src/ReactChildFiber.old.js b/packages/react-reconciler/src/ReactChildFiber.old.js index 80b4fc3551..f1e39112b5 100644 --- a/packages/react-reconciler/src/ReactChildFiber.old.js +++ b/packages/react-reconciler/src/ReactChildFiber.old.js @@ -21,15 +21,7 @@ import { REACT_PORTAL_TYPE, REACT_LAZY_TYPE, } from 'shared/ReactSymbols'; -import { - FunctionComponent, - ClassComponent, - HostText, - HostPortal, - ForwardRef, - Fragment, - SimpleMemoComponent, -} from './ReactWorkTags'; +import {ClassComponent, HostText, HostPortal, Fragment} from './ReactWorkTags'; import invariant from 'shared/invariant'; import isArray from 'shared/isArray'; import { @@ -1302,36 +1294,6 @@ function ChildReconciler(shouldTrackSideEffects) { warnOnFunctionType(returnFiber); } } - if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) { - // If the new child is undefined, and the return fiber is a composite - // component, throw an error. If Fiber return types are disabled, - // we already threw above. - switch (returnFiber.tag) { - case ClassComponent: { - if (__DEV__) { - const instance = returnFiber.stateNode; - if (instance.render._isMockFunction) { - // We allow auto-mocks to proceed as if they're returning null. - break; - } - } - } - // Intentionally fall through to the next case, which handles both - // functions and classes - // eslint-disable-next-lined no-fallthrough - case FunctionComponent: - case ForwardRef: - case SimpleMemoComponent: { - invariant( - false, - '%s(...): Nothing was returned from render. This usually means a ' + - 'return statement is missing. Or, to render nothing, ' + - 'return null.', - getComponentNameFromFiber(returnFiber) || 'Component', - ); - } - } - } // Remaining cases are all treated as empty. return deleteRemainingChildren(returnFiber, currentFirstChild); diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index 9e2874eea4..95074071da 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -554,16 +554,6 @@ function shouldConstruct(Component) { return Component.prototype && Component.prototype.isReactComponent; } -function invalidRenderResult(type: any): void { - invariant( - false, - '%s(...): Nothing was returned from render. This usually means a ' + - 'return statement is missing. Or, to render nothing, ' + - 'return null.', - getComponentNameFromType(type) || 'Component', - ); -} - function renderWithHooks( request: Request, task: Task, @@ -574,11 +564,7 @@ function renderWithHooks( const componentIdentity = {}; prepareToUseHooks(componentIdentity); const result = Component(props, secondArg); - const children = finishHooks(Component, props, result, secondArg); - if (children === undefined) { - invalidRenderResult(Component); - } - return children; + return finishHooks(Component, props, result, secondArg); } function finishClassComponent( @@ -589,13 +575,6 @@ function finishClassComponent( props: any, ): ReactNodeList { const nextChildren = instance.render(); - if (nextChildren === undefined) { - if (__DEV__ && instance.render._isMockFunction) { - // We allow auto-mocks to proceed as if they're returning null. - } else { - invalidRenderResult(Component); - } - } if (__DEV__) { if (instance.props !== props) { diff --git a/packages/shared/__tests__/ReactError-test.internal.js b/packages/shared/__tests__/ReactError-test.internal.js index 0672471ea7..c40e62c6f3 100644 --- a/packages/shared/__tests__/ReactError-test.internal.js +++ b/packages/shared/__tests__/ReactError-test.internal.js @@ -46,18 +46,12 @@ describe('ReactError', () => { ); }); - // @gate build === "production" it('should serialize arguments', () => { function Oops() { return; } Oops.displayName = '#wtf'; const container = document.createElement('div'); - expect(() => ReactDOM.render(, container)).toThrowError( - 'Minified React error #152; visit ' + - 'https://reactjs.org/docs/error-decoder.html?invariant=152&args[]=%23wtf' + - ' for the full message or use the non-minified dev environment' + - ' for full errors and additional helpful warnings.', - ); + expect(() => ReactDOM.render(, container)).not.toThrowError(); }); });