diff --git a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js
index a707a8df6cd..330b6136449 100644
--- a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js
+++ b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js
@@ -338,6 +338,11 @@ describe('ReactCompositeComponent', () => {
'Change ClassWithRenderNotExtended to extend React.Component instead.',
);
}).toThrow(TypeError);
+
+ // Test deduplication
+ expect(() => {
+ ReactDOM.render(, container);
+ }).toThrow(TypeError);
});
it('should warn about `setState` in render', () => {
diff --git a/packages/react-dom/src/__tests__/ReactServerRendering-test.js b/packages/react-dom/src/__tests__/ReactServerRendering-test.js
index cfba21e0523..cfc17f7e28a 100644
--- a/packages/react-dom/src/__tests__/ReactServerRendering-test.js
+++ b/packages/react-dom/src/__tests__/ReactServerRendering-test.js
@@ -554,4 +554,27 @@ describe('ReactDOMServer', () => {
'The experimental Call and Return types are not currently supported by the server renderer.',
);
});
+
+ it('should warn when server rendering a class with a render method that does not extend React.Component', () => {
+ class ClassWithRenderNotExtended {
+ render() {
+ return
;
+ }
+ }
+
+ expect(() => {
+ expect(() =>
+ ReactDOMServer.renderToString(),
+ ).toWarnDev(
+ 'Warning: The component appears to have a render method, ' +
+ "but doesn't extend React.Component. This is likely to cause errors. " +
+ 'Change ClassWithRenderNotExtended to extend React.Component instead.',
+ );
+ }).toThrow(TypeError);
+
+ // Test deduplication
+ expect(() => {
+ ReactDOMServer.renderToString();
+ }).toThrow(TypeError);
+ });
});
diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js
index cc939b691b0..526771f49ca 100644
--- a/packages/react-dom/src/server/ReactPartialRenderer.js
+++ b/packages/react-dom/src/server/ReactPartialRenderer.js
@@ -122,6 +122,7 @@ let didWarnDefaultSelectValue = false;
let didWarnDefaultTextareaValue = false;
let didWarnInvalidOptionChildren = false;
const didWarnAboutNoopUpdateForComponent = {};
+const didWarnAboutBadClass = {};
const valuePropNames = ['value', 'defaultValue'];
const newlineEatingTags = {
listing: true,
@@ -421,6 +422,25 @@ function resolve(
if (shouldConstruct(Component)) {
inst = new Component(element.props, publicContext, updater);
} else {
+ if (__DEV__) {
+ if (
+ Component.prototype &&
+ typeof Component.prototype.render === 'function'
+ ) {
+ const componentName = getComponentName(Component) || 'Unknown';
+
+ if (!didWarnAboutBadClass[componentName]) {
+ warning(
+ false,
+ "The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
+ 'This is likely to cause errors. Change %s to extend React.Component instead.',
+ componentName,
+ componentName,
+ );
+ didWarnAboutBadClass[componentName] = true;
+ }
+ }
+ }
inst = Component(element.props, publicContext, updater);
if (inst == null || inst.render == null) {
child = inst;
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js
index 6c962576e02..415e2697147 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.js
@@ -60,9 +60,11 @@ import {
import {NoWork, Never} from './ReactFiberExpirationTime';
let warnedAboutStatelessRefs;
+let didWarnAboutBadClass;
if (__DEV__) {
warnedAboutStatelessRefs = {};
+ didWarnAboutBadClass = {};
}
export default function(
@@ -458,14 +460,18 @@ export default function(
if (__DEV__) {
if (fn.prototype && typeof fn.prototype.render === 'function') {
- const componentName = getComponentName(workInProgress);
- warning(
- false,
- "The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
- 'This is likely to cause errors. Change %s to extend React.Component instead.',
- componentName,
- componentName,
- );
+ const componentName = getComponentName(workInProgress) || 'Unknown';
+
+ if (!didWarnAboutBadClass[componentName]) {
+ warning(
+ false,
+ "The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
+ 'This is likely to cause errors. Change %s to extend React.Component instead.',
+ componentName,
+ componentName,
+ );
+ didWarnAboutBadClass[componentName] = true;
+ }
}
ReactCurrentOwner.current = workInProgress;
value = fn(props, context);