diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponent.js b/packages/react-dom-bindings/src/client/ReactDOMComponent.js
index 1b25e3727023..628ee56454e5 100644
--- a/packages/react-dom-bindings/src/client/ReactDOMComponent.js
+++ b/packages/react-dom-bindings/src/client/ReactDOMComponent.js
@@ -60,6 +60,7 @@ import getAttributeAlias from '../shared/getAttributeAlias';
import possibleStandardNames from '../shared/possibleStandardNames';
import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook';
import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook';
+import {validateProperties as validateTitleProperties} from '../shared/ReactDOMTitle';
import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';
import sanitizeURL from '../shared/sanitizeURL';
@@ -99,6 +100,7 @@ function validatePropertiesInDevelopment(type: string, props: any) {
registrationNameDependencies,
possibleRegistrationNames,
});
+ validateTitleProperties(type, props);
if (
props.contentEditable &&
!props.suppressContentEditableWarning &&
diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
index 73ce8d3dd29f..ba1b6c109d4f 100644
--- a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
+++ b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
@@ -67,6 +67,7 @@ import getAttributeAlias from '../shared/getAttributeAlias';
import {checkControlledValueProps} from '../shared/ReactControlledValuePropTypes';
import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook';
import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook';
+import {validateProperties as validateTitleProperties} from '../shared/ReactDOMTitle';
import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';
import warnValidStyle from '../shared/warnValidStyle';
import {getCrossOriginString} from '../shared/crossOriginStrings';
@@ -3535,51 +3536,7 @@ function pushTitle(
const noscriptTagInScope = formatContext.tagScope & NOSCRIPT_SCOPE;
const isFallback = formatContext.tagScope & FALLBACK_SCOPE;
if (__DEV__) {
- if (hasOwnProperty.call(props, 'children')) {
- const children = props.children;
-
- const child = Array.isArray(children)
- ? children.length < 2
- ? children[0]
- : null
- : children;
-
- if (Array.isArray(children) && children.length > 1) {
- console.error(
- 'React expects the `children` prop of
tags to be a string, number, bigint, or object with a novel `toString` method but found an Array with length %s instead.' +
- ' Browsers treat all child Nodes of tags as Text content and React expects to be able to convert `children` of tags to a single string value' +
- ' which is why Arrays of length greater than 1 are not supported. When using JSX it can be common to combine text nodes and value nodes.' +
- ' For example: hello {nameOfUser}. While not immediately apparent, `children` in this case is an Array with length 2. If your `children` prop' +
- ' is using this form try rewriting it using a template string: {`hello ${nameOfUser}`}.',
- children.length,
- );
- } else if (typeof child === 'function' || typeof child === 'symbol') {
- const childType =
- typeof child === 'function' ? 'a Function' : 'a Sybmol';
- console.error(
- 'React expect children of tags to be a string, number, bigint, or object with a novel `toString` method but found %s instead.' +
- ' Browsers treat all child Nodes of tags as Text content and React expects to be able to convert children of ' +
- ' tags to a single string value.',
- childType,
- );
- } else if (child && child.toString === {}.toString) {
- if (child.$$typeof != null) {
- console.error(
- 'React expects the `children` prop of tags to be a string, number, bigint, or object with a novel `toString` method but found an object that appears to be' +
- ' a React element which never implements a suitable `toString` method. Browsers treat all child Nodes of tags as Text content and React expects to' +
- ' be able to convert children of tags to a single string value which is why rendering React elements is not supported. If the `children` of is' +
- ' a React Component try moving the tag into that component. If the `children` of is some HTML markup change it to be Text only to be valid HTML.',
- );
- } else {
- console.error(
- 'React expects the `children` prop of tags to be a string, number, bigint, or object with a novel `toString` method but found an object that does not implement' +
- ' a suitable `toString` method. Browsers treat all child Nodes of tags as Text content and React expects to be able to convert children of tags' +
- ' to a single string value. Using the default `toString` method available on every object is almost certainly an error. Consider whether the `children` of this ' +
- ' is an object in error and change it to a string or number value if so. Otherwise implement a `toString` method that React can use to produce a valid .',
- );
- }
- }
- }
+ validateTitleProperties('title', props);
}
if (
diff --git a/packages/react-dom-bindings/src/shared/ReactDOMTitle.js b/packages/react-dom-bindings/src/shared/ReactDOMTitle.js
new file mode 100644
index 000000000000..ca1ad2738f5b
--- /dev/null
+++ b/packages/react-dom-bindings/src/shared/ReactDOMTitle.js
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+const hasOwnProperty = Object.prototype.hasOwnProperty;
+
+export function validateProperties(type: string, props: Object) {
+ if (type !== 'title') {
+ return;
+ }
+
+ if (__DEV__) {
+ if (hasOwnProperty.call(props, 'children')) {
+ const children = props.children;
+
+ const child = Array.isArray(children)
+ ? children.length < 2
+ ? children[0]
+ : null
+ : children;
+
+ if (Array.isArray(children) && children.length > 1) {
+ console.error(
+ 'React expects the `children` prop of tags to be a string, number, bigint, or object with a novel `toString` method but found an Array with length %s instead.' +
+ ' Browsers treat all child Nodes of tags as Text content and React expects to be able to convert `children` of tags to a single string value' +
+ ' which is why Arrays of length greater than 1 are not supported. When using JSX it can be common to combine text nodes and value nodes.' +
+ ' For example: hello {nameOfUser}. While not immediately apparent, `children` in this case is an Array with length 2. If your `children` prop' +
+ ' is using this form try rewriting it using a template string: {`hello ${nameOfUser}`}.',
+ children.length,
+ );
+ } else if (typeof child === 'function' || typeof child === 'symbol') {
+ const childType =
+ typeof child === 'function' ? 'a Function' : 'a Symbol';
+ console.error(
+ 'React expect children of tags to be a string, number, bigint, or object with a novel `toString` method but found %s instead.' +
+ ' Browsers treat all child Nodes of tags as Text content and React expects to be able to convert children of ' +
+ ' tags to a single string value.',
+ childType,
+ );
+ } else if (child && child.toString === {}.toString) {
+ if (child.$$typeof != null) {
+ console.error(
+ 'React expects the `children` prop of tags to be a string, number, bigint, or object with a novel `toString` method but found an object that appears to be' +
+ ' a React element which never implements a suitable `toString` method. Browsers treat all child Nodes of tags as Text content and React expects to' +
+ ' be able to convert children of tags to a single string value which is why rendering React elements is not supported. If the `children` of is' +
+ ' a React Component try moving the tag into that component. If the `children` of is some HTML markup change it to be Text only to be valid HTML.',
+ );
+ } else {
+ console.error(
+ 'React expects the `children` prop of tags to be a string, number, bigint, or object with a novel `toString` method but found an object that does not implement' +
+ ' a suitable `toString` method. Browsers treat all child Nodes of tags as Text content and React expects to be able to convert children of tags' +
+ ' to a single string value. Using the default `toString` method available on every object is almost certainly an error. Consider whether the `children` of this ' +
+ ' is an object in error and change it to a string or number value if so. Otherwise implement a `toString` method that React can use to produce a valid .',
+ );
+ }
+ }
+ }
+ }
+}
diff --git a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js
index 0f0986dde8e3..7cbdf575b3ad 100644
--- a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js
@@ -2700,6 +2700,24 @@ describe('ReactDOMComponent', () => {
// without access to the event system (which we don't bundle).
});
+ it('should warn when has an Array of children (client)', async () => {
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+ React.createElement('title', null, 'My App', ' | Home'),
+ );
+ });
+ assertConsoleErrorDev([
+ 'React expects the `children` prop of tags to be a string, number, bigint, or object with a novel `toString` method but found an Array with length 2 instead.' +
+ ' Browsers treat all child Nodes of tags as Text content and React expects to be able to convert `children` of tags to a single string value' +
+ ' which is why Arrays of length greater than 1 are not supported. When using JSX it can be common to combine text nodes and value nodes.' +
+ ' For example: hello {nameOfUser}. While not immediately apparent, `children` in this case is an Array with length 2. If your `children` prop' +
+ ' is using this form try rewriting it using a template string: {`hello ${nameOfUser}`}.\n' +
+ ' in title (at **)',
+ ]);
+ });
+
it('should warn about incorrect casing on properties', async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
index 6ff107786f3b..223369b02792 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
@@ -5926,6 +5926,21 @@ describe('ReactDOMFizzServer', () => {
},
});
await waitForAll([]);
+ assertConsoleErrorDev(
+ [
+ 'React expects the `children` prop of tags to be a string, number, bigint, ' +
+ 'or object with a novel `toString` method but found an Array with length 2 instead. ' +
+ 'Browsers treat all child Nodes of tags as Text content and React expects ' +
+ 'to be able to convert `children` of tags to a single string value which is why ' +
+ 'Arrays of length greater than 1 are not supported. ' +
+ 'When using JSX it can be common to combine text nodes and value nodes. ' +
+ 'For example: hello {nameOfUser}. ' +
+ 'While not immediately apparent, `children` in this case is an Array with length 2. ' +
+ 'If your `children` prop is using this form try rewriting it using a template string: ' +
+ '{`hello ${nameOfUser}`}.',
+ ],
+ {withoutStack: true},
+ );
expect(errors).toEqual([]);
// with float, the title doesn't render on the client or on the server
expect(getVisibleChildren(document.head)).toEqual();
@@ -5973,6 +5988,19 @@ describe('ReactDOMFizzServer', () => {
},
});
await waitForAll([]);
+ assertConsoleErrorDev(
+ [
+ 'React expects the `children` prop of tags to be a string, number, bigint, ' +
+ 'or object with a novel `toString` method but found an object that appears to be a ' +
+ 'React element which never implements a suitable `toString` method. ' +
+ 'Browsers treat all child Nodes of tags as Text content and React expects ' +
+ 'to be able to convert children of tags to a single string value which is ' +
+ 'why rendering React elements is not supported. If the `children` of is a ' +
+ 'React Component try moving the tag into that component. ' +
+ 'If the `children` of is some HTML markup change it to be Text only to be valid HTML.',
+ ],
+ {withoutStack: true},
+ );
expect(errors).toEqual([]);
// object titles are toStringed when float is on
expect(getVisibleChildren(document.head)).toEqual(
@@ -6016,6 +6044,19 @@ describe('ReactDOMFizzServer', () => {
},
});
await waitForAll([]);
+ assertConsoleErrorDev(
+ [
+ 'React expects the `children` prop of tags to be a string, number, bigint, ' +
+ 'or object with a novel `toString` method but found an object that does not implement a ' +
+ 'suitable `toString` method. Browsers treat all child Nodes of tags as Text ' +
+ 'content and React expects to be able to convert children of tags to a single string value. ' +
+ 'Using the default `toString` method available on every object is almost certainly an error. ' +
+ 'Consider whether the `children` of this is an object in error and change it to a ' +
+ 'string or number value if so. Otherwise implement a `toString` method that React can ' +
+ 'use to produce a valid .',
+ ],
+ {withoutStack: true},
+ );
expect(errors).toEqual([]);
// object titles are toStringed when float is on
expect(getVisibleChildren(document.head)).toEqual(