diff --git a/packages/react-dom/src/__tests__/ReactDOMInput-test.js b/packages/react-dom/src/__tests__/ReactDOMInput-test.js
index 5afa9dcf7aa..3710fa40081 100644
--- a/packages/react-dom/src/__tests__/ReactDOMInput-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMInput-test.js
@@ -400,14 +400,20 @@ describe('ReactDOMInput', () => {
it('should display "true" for `defaultValue` of `true`', () => {
let stub = ;
- const node = ReactDOM.render(stub, container);
+ let node;
+ expect(() => (node = ReactDOM.render(stub, container))).toWarnDev(
+ 'Received `true` for a non-boolean attribute `defaultValue`',
+ );
expect(node.value).toBe('true');
});
it('should display "false" for `defaultValue` of `false`', () => {
let stub = ;
- const node = ReactDOM.render(stub, container);
+ let node;
+ expect(() => (node = ReactDOM.render(stub, container))).toWarnDev(
+ 'Received `false` for a non-boolean attribute `defaultValue`',
+ );
expect(node.value).toBe('false');
});
@@ -1641,22 +1647,25 @@ describe('ReactDOMInput', () => {
});
it('treats initial Symbol defaultValue as an empty string', function() {
- ReactDOM.render(, container);
+ expect(() =>
+ ReactDOM.render(, container),
+ ).toWarnDev('Invalid value for prop `defaultValue`');
+
const node = container.firstChild;
expect(node.value).toBe('');
expect(node.getAttribute('value')).toBe('');
- // TODO: we should warn here.
});
it('treats updated Symbol defaultValue as an empty string', function() {
ReactDOM.render(, container);
- ReactDOM.render(, container);
+ expect(() =>
+ ReactDOM.render(, container),
+ ).toWarnDev('Invalid value for prop `defaultValue`');
const node = container.firstChild;
expect(node.value).toBe('foo');
expect(node.getAttribute('value')).toBe('');
- // TODO: we should warn here.
});
});
@@ -1689,22 +1698,24 @@ describe('ReactDOMInput', () => {
});
it('treats initial function defaultValue as an empty string', function() {
- ReactDOM.render( {}} />, container);
+ expect(() =>
+ ReactDOM.render( {}} />, container),
+ ).toWarnDev('Invalid value for prop `defaultValue`');
const node = container.firstChild;
expect(node.value).toBe('');
expect(node.getAttribute('value')).toBe('');
- // TODO: we should warn here.
});
it('treats updated function defaultValue as an empty string', function() {
ReactDOM.render(, container);
- ReactDOM.render( {}} />, container);
+ expect(() =>
+ ReactDOM.render( {}} />, container),
+ ).toWarnDev('Invalid value for prop `defaultValue`');
const node = container.firstChild;
expect(node.value).toBe('foo');
expect(node.getAttribute('value')).toBe('');
- // TODO: we should warn here.
});
});
diff --git a/packages/react-dom/src/__tests__/ReactDOMTextarea-test.js b/packages/react-dom/src/__tests__/ReactDOMTextarea-test.js
index fa5d717974a..0c40c1ff36f 100644
--- a/packages/react-dom/src/__tests__/ReactDOMTextarea-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMTextarea-test.js
@@ -63,7 +63,10 @@ describe('ReactDOMTextarea', () => {
it('should display "false" for `defaultValue` of `false`', () => {
const stub = ;
- const node = renderTextarea(stub);
+ let node;
+ expect(() => (node = renderTextarea(stub))).toWarnDev(
+ 'Received `false` for a non-boolean attribute `defaultValue`',
+ );
expect(node.value).toBe('false');
});
diff --git a/packages/react-dom/src/shared/DOMProperty.js b/packages/react-dom/src/shared/DOMProperty.js
index cef4609497f..b65d445a42b 100644
--- a/packages/react-dom/src/shared/DOMProperty.js
+++ b/packages/react-dom/src/shared/DOMProperty.js
@@ -51,6 +51,7 @@ export type PropertyInfo = {|
+mustUseProperty: boolean,
+propertyName: string,
+type: PropertyType,
+ +shouldWarnInDev: boolean,
|};
/* eslint-disable max-len */
@@ -115,7 +116,13 @@ export function shouldRemoveAttributeWithWarning(
propertyInfo: PropertyInfo | null,
isCustomComponentTag: boolean,
): boolean {
- if (propertyInfo !== null && propertyInfo.type === RESERVED) {
+ // Do not validate data types for reserved props
+ // (excluding defaultValue)
+ if (
+ propertyInfo !== null &&
+ propertyInfo.type === RESERVED &&
+ !propertyInfo.shouldWarnInDev
+ ) {
return false;
}
switch (typeof value) {
@@ -186,6 +193,7 @@ function PropertyInfoRecord(
mustUseProperty: boolean,
attributeName: string,
attributeNamespace: string | null,
+ shouldWarnInDev: boolean = false,
) {
this.acceptsBooleans =
type === BOOLEANISH_STRING ||
@@ -196,6 +204,7 @@ function PropertyInfoRecord(
this.mustUseProperty = mustUseProperty;
this.propertyName = name;
this.type = type;
+ this.shouldWarnInDev = shouldWarnInDev;
}
// When adding attributes to this list, be sure to also add them to
@@ -223,6 +232,7 @@ const properties = {};
false, // mustUseProperty
name, // attributeName
null, // attributeNamespace
+ name === 'defaultValue', // shouldWarnInDev
);
});
diff --git a/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js b/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js
index 9e7fda4e6cd..100cb22755e 100644
--- a/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js
+++ b/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js
@@ -214,12 +214,6 @@ if (__DEV__) {
return true;
}
- // Now that we've validated casing, do not validate
- // data types for reserved props
- if (isReserved) {
- return true;
- }
-
// Warn when a known attribute is a bad type
if (shouldRemoveAttributeWithWarning(name, value, propertyInfo, false)) {
warnedProperties[name] = true;