From 404a1d022ab0f450d70885e9fe0ab105a485109e Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 6 Aug 2020 14:50:05 -0400 Subject: [PATCH] Warn about undefined return value for memo and forwardRef --- .../src/__tests__/ReactEmptyComponent-test.js | 20 +++++++++++++++++++ .../src/ReactChildFiber.new.js | 10 +++++++--- .../src/ReactChildFiber.old.js | 10 +++++++--- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactEmptyComponent-test.js b/packages/react-dom/src/__tests__/ReactEmptyComponent-test.js index 10e95f7cf7ba0..0c0b1d1a92739 100644 --- a/packages/react-dom/src/__tests__/ReactEmptyComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactEmptyComponent-test.js @@ -316,4 +316,24 @@ describe('ReactEmptyComponent', () => { 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.', + ); + }); + + it('should warn about React.memo that returns undefined', () => { + const Empty = () => {}; + const EmptyMemo = React.memo(Empty); + + expect(() => { + ReactTestUtils.renderIntoDocument(); + }).toThrowError('Empty(...): Nothing was returned from render.'); + }); }); diff --git a/packages/react-reconciler/src/ReactChildFiber.new.js b/packages/react-reconciler/src/ReactChildFiber.new.js index e39660ba0b562..3bef3545fc3a2 100644 --- a/packages/react-reconciler/src/ReactChildFiber.new.js +++ b/packages/react-reconciler/src/ReactChildFiber.new.js @@ -29,7 +29,9 @@ import { ClassComponent, HostText, HostPortal, + ForwardRef, Fragment, + SimpleMemoComponent, Block, } from './ReactWorkTags'; import invariant from 'shared/invariant'; @@ -1393,14 +1395,16 @@ function ChildReconciler(shouldTrackSideEffects) { // Intentionally fall through to the next case, which handles both // functions and classes // eslint-disable-next-lined no-fallthrough - case FunctionComponent: { - const Component = returnFiber.type; + case Block: + 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.', - Component.displayName || Component.name || 'Component', + getComponentName(returnFiber.type) || 'Component', ); } } diff --git a/packages/react-reconciler/src/ReactChildFiber.old.js b/packages/react-reconciler/src/ReactChildFiber.old.js index fc597b0a7d36e..6af9a6ab9fee2 100644 --- a/packages/react-reconciler/src/ReactChildFiber.old.js +++ b/packages/react-reconciler/src/ReactChildFiber.old.js @@ -29,7 +29,9 @@ import { ClassComponent, HostText, HostPortal, + ForwardRef, Fragment, + SimpleMemoComponent, Block, } from './ReactWorkTags'; import invariant from 'shared/invariant'; @@ -1385,14 +1387,16 @@ function ChildReconciler(shouldTrackSideEffects) { // Intentionally fall through to the next case, which handles both // functions and classes // eslint-disable-next-lined no-fallthrough - case FunctionComponent: { - const Component = returnFiber.type; + case Block: + 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.', - Component.displayName || Component.name || 'Component', + getComponentName(returnFiber.type) || 'Component', ); } }