diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js index b03f9ed36425b..f50253904d698 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js @@ -871,5 +871,71 @@ describe('ReactDOMServerIntegration', () => { : ''), ); }); + + describe('badly-typed elements', function() { + itThrowsWhenRendering( + 'object', + async render => { + let EmptyComponent = {}; + expect(() => { + EmptyComponent = ; + }).toWarnDev( + 'Warning: React.createElement: type is invalid -- expected a string ' + + '(for built-in components) or a class/function (for composite ' + + 'components) but got: object. You likely forgot to export your ' + + "component from the file it's defined in, or you might have mixed up " + + 'default and named imports.', + ); + await render(EmptyComponent); + }, + 'Element type is invalid: expected a string (for built-in components) or a class/function ' + + '(for composite components) but got: object.' + + (__DEV__ + ? " You likely forgot to export your component from the file it's defined in, " + + 'or you might have mixed up default and named imports.' + : ''), + ); + + itThrowsWhenRendering( + 'null', + async render => { + let NullComponent = null; + expect(() => { + NullComponent = ; + }).toWarnDev( + 'Warning: React.createElement: type is invalid -- expected a string ' + + '(for built-in components) or a class/function (for composite ' + + 'components) but got: null.', + ); + await render(NullComponent); + }, + 'Element type is invalid: expected a string (for built-in components) or a class/function ' + + '(for composite components) but got: null', + ); + + itThrowsWhenRendering( + 'undefined', + async render => { + let UndefinedComponent = undefined; + expect(() => { + UndefinedComponent = ; + }).toWarnDev( + 'Warning: React.createElement: type is invalid -- expected a string ' + + '(for built-in components) or a class/function (for composite ' + + 'components) but got: undefined. You likely forgot to export your ' + + "component from the file it's defined in, or you might have mixed up " + + 'default and named imports.', + ); + + await render(UndefinedComponent); + }, + 'Element type is invalid: expected a string (for built-in components) or a class/function ' + + '(for composite components) but got: undefined.' + + (__DEV__ + ? " You likely forgot to export your component from the file it's defined in, " + + 'or you might have mixed up default and named imports.' + : ''), + ); + }); }); }); diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index 656f20fc7144a..e774aeaace83d 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -889,12 +889,33 @@ class ReactDOMServerRenderer { break; } } + + let info = ''; + if (__DEV__) { + const owner = nextElement._owner; + if ( + elementType === undefined || + (typeof elementType === 'object' && + elementType !== null && + Object.keys(elementType).length === 0) + ) { + info += + ' You likely forgot to export your component from the file ' + + "it's defined in, or you might have mixed up default and " + + 'named imports.'; + } + const ownerName = owner ? getComponentName(owner) : null; + if (ownerName) { + info += '\n\nCheck the render method of `' + ownerName + '`.'; + } + } invariant( false, 'Element type is invalid: expected a string (for built-in ' + 'components) or a class/function (for composite components) ' + - 'but got: %s.', + 'but got: %s.%s', elementType == null ? elementType : typeof elementType, + info, ); } }