diff --git a/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js b/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js index b09789d436fb7..14699940e31fd 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js +++ b/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js @@ -587,17 +587,27 @@ export function getResource( return null; } case 'title': { - let child = pendingProps.children; - if (Array.isArray(child) && child.length === 1) { - child = child[0]; + const children = pendingProps.children; + let child; + if (Array.isArray(children)) { + child = children.length === 1 ? children[0] : null; + } else { + child = children; } - if (typeof child === 'string' || typeof child === 'number') { + if ( + typeof child !== 'function' && + typeof child !== 'symbol' && + child !== null && + child !== undefined + ) { + // eslint-disable-next-line react-internal/safe-string-coercion + const childString = '' + (child: any); const headRoot: Document = getDocumentFromRoot(resourceRoot); const headResources = getResourcesFromRoot(headRoot).head; - const key = getTitleKey(child); + const key = getTitleKey(childString); let resource = headResources.get(key); if (!resource) { - const titleProps = titlePropsFromRawProps(child, pendingProps); + const titleProps = titlePropsFromRawProps(childString, pendingProps); resource = { type: 'title', props: titleProps, diff --git a/packages/react-dom-bindings/src/server/ReactDOMFloatServer.js b/packages/react-dom-bindings/src/server/ReactDOMFloatServer.js index 2805b629d4b48..fe75f9833745a 100644 --- a/packages/react-dom-bindings/src/server/ReactDOMFloatServer.js +++ b/packages/react-dom-bindings/src/server/ReactDOMFloatServer.js @@ -652,17 +652,27 @@ export function resourcesFromElement(type: string, props: Props): boolean { const resources = currentResources; switch (type) { case 'title': { - let child = props.children; - if (Array.isArray(child) && child.length === 1) { - child = child[0]; + const children = props.children; + let child; + if (Array.isArray(children)) { + child = children.length === 1 ? children[0] : null; + } else { + child = children; } - if (typeof child === 'string' || typeof child === 'number') { - const key = 'title::' + child; + if ( + typeof child !== 'function' && + typeof child !== 'symbol' && + child !== null && + child !== undefined + ) { + // eslint-disable-next-line react-internal/safe-string-coercion + const childString = '' + (child: any); + const key = 'title::' + childString; let resource = resources.headsMap.get(key); if (!resource) { resource = { type: 'title', - props: titlePropsFromRawProps(child, props), + props: titlePropsFromRawProps(childString, props), flushed: false, }; resources.headsMap.set(key, resource); diff --git a/packages/react-dom-bindings/src/server/ReactDOMServerFormatConfig.js b/packages/react-dom-bindings/src/server/ReactDOMServerFormatConfig.js index c66292d801835..14fba1762ca9a 100644 --- a/packages/react-dom-bindings/src/server/ReactDOMServerFormatConfig.js +++ b/packages/react-dom-bindings/src/server/ReactDOMServerFormatConfig.js @@ -1473,12 +1473,19 @@ function pushTitleImpl( } target.push(endOfStartTag); - const child = - Array.isArray(children) && children.length < 2 - ? children[0] || null - : children; - if (typeof child === 'string' || typeof child === 'number') { - target.push(stringToChunk(escapeTextForBrowser(child))); + const child = Array.isArray(children) + ? children.length < 2 + ? children[0] + : null + : children; + if ( + typeof child !== 'function' && + typeof child !== 'symbol' && + child !== null && + child !== undefined + ) { + // eslint-disable-next-line react-internal/safe-string-coercion + target.push(stringToChunk(escapeTextForBrowser('' + child))); } target.push(endTag1, stringToChunk('title'), endTag2); return null; diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index b900f3452b4ee..79455a7225df8 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -5144,8 +5144,10 @@ describe('ReactDOMFizzServer', () => { } if (gate(flags => flags.enableFloat)) { - // invalid titles are not emitted on the server when float is on - expect(getVisibleChildren(container)).toEqual(undefined); + // object titles are toStringed when float is on + expect(getVisibleChildren(container)).toEqual( +