Skip to content

Commit bb77852

Browse files
authored
[Fizz] Fix children rendering in custom elements with enableCustomElementPropertySupport (#27511)
The `enableCustomElementPropertySupport` flag changes React's handling of custom elements in a way that is more useful that just treating every prop as an attribute. However when server rendering we have no choice but to serialize props as attributes. When this flag is on and React supports more prop types on the client like functions and objects the server implementation needs to be a bit more naunced in how it renders these components. With this flag on `false`, function, and object props are omitted entirely and `true` is normalized to `""`. There was a bug however in the implementation which caused children more complex than a single string to be omitted because it matched the object type filter. This change reorganizes the code a bit to put these filters in the default prop handline case, leaving children, style, and innerHTML to be handled via normal logic. fixes: #27286
1 parent ea8a861 commit bb77852

File tree

2 files changed

+45
-22
lines changed

2 files changed

+45
-22
lines changed

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

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3134,32 +3134,13 @@ function pushStartCustomElement(
31343134

31353135
let children = null;
31363136
let innerHTML = null;
3137-
for (let propKey in props) {
3137+
for (const propKey in props) {
31383138
if (hasOwnProperty.call(props, propKey)) {
31393139
let propValue = props[propKey];
31403140
if (propValue == null) {
31413141
continue;
31423142
}
3143-
if (
3144-
enableCustomElementPropertySupport &&
3145-
(typeof propValue === 'function' || typeof propValue === 'object')
3146-
) {
3147-
// It is normal to render functions and objects on custom elements when
3148-
// client rendering, but when server rendering the output isn't useful,
3149-
// so skip it.
3150-
continue;
3151-
}
3152-
if (enableCustomElementPropertySupport && propValue === false) {
3153-
continue;
3154-
}
3155-
if (enableCustomElementPropertySupport && propValue === true) {
3156-
propValue = '';
3157-
}
3158-
if (enableCustomElementPropertySupport && propKey === 'className') {
3159-
// className gets rendered as class on the client, so it should be
3160-
// rendered as class on the server.
3161-
propKey = 'class';
3162-
}
3143+
let attributeName = propKey;
31633144
switch (propKey) {
31643145
case 'children':
31653146
children = propValue;
@@ -3174,15 +3155,31 @@ function pushStartCustomElement(
31743155
case 'suppressHydrationWarning':
31753156
// Ignored. These are built-in to React on the client.
31763157
break;
3158+
case 'className':
3159+
if (enableCustomElementPropertySupport) {
3160+
// className gets rendered as class on the client, so it should be
3161+
// rendered as class on the server.
3162+
attributeName = 'class';
3163+
}
3164+
// intentional fallthrough
31773165
default:
31783166
if (
31793167
isAttributeNameSafe(propKey) &&
31803168
typeof propValue !== 'function' &&
31813169
typeof propValue !== 'symbol'
31823170
) {
3171+
if (enableCustomElementPropertySupport) {
3172+
if (propValue === false) {
3173+
continue;
3174+
} else if (propValue === true) {
3175+
propValue = '';
3176+
} else if (typeof propValue === 'object') {
3177+
continue;
3178+
}
3179+
}
31833180
target.push(
31843181
attributeSeparator,
3185-
stringToChunk(propKey),
3182+
stringToChunk(attributeName),
31863183
attributeAssign,
31873184
stringToChunk(escapeTextForBrowser(propValue)),
31883185
attributeEnd,

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3624,6 +3624,32 @@ describe('ReactDOMFizzServer', () => {
36243624
);
36253625
});
36263626

3627+
// bugfix: https://github.com/facebook/react/issues/27286 affecting enableCustomElementPropertySupport flag
3628+
it('can render custom elements with children on ther server', async () => {
3629+
await act(() => {
3630+
renderToPipeableStream(
3631+
<html>
3632+
<body>
3633+
<my-element>
3634+
<div>foo</div>
3635+
</my-element>
3636+
</body>
3637+
</html>,
3638+
).pipe(writable);
3639+
});
3640+
3641+
expect(getVisibleChildren(document)).toEqual(
3642+
<html>
3643+
<head />
3644+
<body>
3645+
<my-element>
3646+
<div>foo</div>
3647+
</my-element>
3648+
</body>
3649+
</html>,
3650+
);
3651+
});
3652+
36273653
describe('error escaping', () => {
36283654
it('escapes error hash, message, and component stack values in directly flushed errors (html escaping)', async () => {
36293655
window.__outlet = {};

0 commit comments

Comments
 (0)