Skip to content

Commit bfcbf33

Browse files
authored
toString children of title (#25838)
children of title can either behave like children or like an attribute. We're kind of treating it more like an attribute now so we should support toString/valueOf like we do on attributes.
1 parent d4bc16a commit bfcbf33

File tree

4 files changed

+53
-22
lines changed

4 files changed

+53
-22
lines changed

packages/react-dom-bindings/src/client/ReactDOMFloatClient.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -587,17 +587,27 @@ export function getResource(
587587
return null;
588588
}
589589
case 'title': {
590-
let child = pendingProps.children;
591-
if (Array.isArray(child) && child.length === 1) {
592-
child = child[0];
590+
const children = pendingProps.children;
591+
let child;
592+
if (Array.isArray(children)) {
593+
child = children.length === 1 ? children[0] : null;
594+
} else {
595+
child = children;
593596
}
594-
if (typeof child === 'string' || typeof child === 'number') {
597+
if (
598+
typeof child !== 'function' &&
599+
typeof child !== 'symbol' &&
600+
child !== null &&
601+
child !== undefined
602+
) {
603+
// eslint-disable-next-line react-internal/safe-string-coercion
604+
const childString = '' + (child: any);
595605
const headRoot: Document = getDocumentFromRoot(resourceRoot);
596606
const headResources = getResourcesFromRoot(headRoot).head;
597-
const key = getTitleKey(child);
607+
const key = getTitleKey(childString);
598608
let resource = headResources.get(key);
599609
if (!resource) {
600-
const titleProps = titlePropsFromRawProps(child, pendingProps);
610+
const titleProps = titlePropsFromRawProps(childString, pendingProps);
601611
resource = {
602612
type: 'title',
603613
props: titleProps,

packages/react-dom-bindings/src/server/ReactDOMFloatServer.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -652,17 +652,27 @@ export function resourcesFromElement(type: string, props: Props): boolean {
652652
const resources = currentResources;
653653
switch (type) {
654654
case 'title': {
655-
let child = props.children;
656-
if (Array.isArray(child) && child.length === 1) {
657-
child = child[0];
655+
const children = props.children;
656+
let child;
657+
if (Array.isArray(children)) {
658+
child = children.length === 1 ? children[0] : null;
659+
} else {
660+
child = children;
658661
}
659-
if (typeof child === 'string' || typeof child === 'number') {
660-
const key = 'title::' + child;
662+
if (
663+
typeof child !== 'function' &&
664+
typeof child !== 'symbol' &&
665+
child !== null &&
666+
child !== undefined
667+
) {
668+
// eslint-disable-next-line react-internal/safe-string-coercion
669+
const childString = '' + (child: any);
670+
const key = 'title::' + childString;
661671
let resource = resources.headsMap.get(key);
662672
if (!resource) {
663673
resource = {
664674
type: 'title',
665-
props: titlePropsFromRawProps(child, props),
675+
props: titlePropsFromRawProps(childString, props),
666676
flushed: false,
667677
};
668678
resources.headsMap.set(key, resource);

packages/react-dom-bindings/src/server/ReactDOMServerFormatConfig.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,12 +1473,19 @@ function pushTitleImpl(
14731473
}
14741474
target.push(endOfStartTag);
14751475

1476-
const child =
1477-
Array.isArray(children) && children.length < 2
1478-
? children[0] || null
1479-
: children;
1480-
if (typeof child === 'string' || typeof child === 'number') {
1481-
target.push(stringToChunk(escapeTextForBrowser(child)));
1476+
const child = Array.isArray(children)
1477+
? children.length < 2
1478+
? children[0]
1479+
: null
1480+
: children;
1481+
if (
1482+
typeof child !== 'function' &&
1483+
typeof child !== 'symbol' &&
1484+
child !== null &&
1485+
child !== undefined
1486+
) {
1487+
// eslint-disable-next-line react-internal/safe-string-coercion
1488+
target.push(stringToChunk(escapeTextForBrowser('' + child)));
14821489
}
14831490
target.push(endTag1, stringToChunk('title'), endTag2);
14841491
return null;

packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5144,8 +5144,10 @@ describe('ReactDOMFizzServer', () => {
51445144
}
51455145

51465146
if (gate(flags => flags.enableFloat)) {
5147-
// invalid titles are not emitted on the server when float is on
5148-
expect(getVisibleChildren(container)).toEqual(undefined);
5147+
// object titles are toStringed when float is on
5148+
expect(getVisibleChildren(container)).toEqual(
5149+
<title>{'[object Object]'}</title>,
5150+
);
51495151
} else {
51505152
expect(getVisibleChildren(container)).toEqual(<title>hello</title>);
51515153
}
@@ -5159,8 +5161,10 @@ describe('ReactDOMFizzServer', () => {
51595161
expect(Scheduler).toFlushAndYield([]);
51605162
expect(errors).toEqual([]);
51615163
if (gate(flags => flags.enableFloat)) {
5162-
// invalid titles are not emitted on the server when float is on
5163-
expect(getVisibleChildren(container)).toEqual(undefined);
5164+
// object titles are toStringed when float is on
5165+
expect(getVisibleChildren(container)).toEqual(
5166+
<title>{'[object Object]'}</title>,
5167+
);
51645168
} else {
51655169
expect(getVisibleChildren(container)).toEqual(<title>hello</title>);
51665170
}

0 commit comments

Comments
 (0)