From 78db6ca7f67cb86541a267ae31a929cc083832f4 Mon Sep 17 00:00:00 2001
From: Josh Story
Date: Thu, 12 Oct 2023 14:47:58 -0700
Subject: [PATCH] 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.
---
.../src/server/ReactFizzConfigDOM.js | 41 +++++++++----------
.../src/__tests__/ReactDOMFizzServer-test.js | 26 ++++++++++++
2 files changed, 45 insertions(+), 22 deletions(-)
diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
index 9c82cb6579231..99cb6db2a12b6 100644
--- a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
+++ b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
@@ -3134,32 +3134,13 @@ function pushStartCustomElement(
let children = null;
let innerHTML = null;
- for (let propKey in props) {
+ for (const propKey in props) {
if (hasOwnProperty.call(props, propKey)) {
let propValue = props[propKey];
if (propValue == null) {
continue;
}
- if (
- enableCustomElementPropertySupport &&
- (typeof propValue === 'function' || typeof propValue === 'object')
- ) {
- // It is normal to render functions and objects on custom elements when
- // client rendering, but when server rendering the output isn't useful,
- // so skip it.
- continue;
- }
- if (enableCustomElementPropertySupport && propValue === false) {
- continue;
- }
- if (enableCustomElementPropertySupport && propValue === true) {
- propValue = '';
- }
- if (enableCustomElementPropertySupport && propKey === 'className') {
- // className gets rendered as class on the client, so it should be
- // rendered as class on the server.
- propKey = 'class';
- }
+ let attributeName = propKey;
switch (propKey) {
case 'children':
children = propValue;
@@ -3174,15 +3155,31 @@ function pushStartCustomElement(
case 'suppressHydrationWarning':
// Ignored. These are built-in to React on the client.
break;
+ case 'className':
+ if (enableCustomElementPropertySupport) {
+ // className gets rendered as class on the client, so it should be
+ // rendered as class on the server.
+ attributeName = 'class';
+ }
+ // intentional fallthrough
default:
if (
isAttributeNameSafe(propKey) &&
typeof propValue !== 'function' &&
typeof propValue !== 'symbol'
) {
+ if (enableCustomElementPropertySupport) {
+ if (propValue === false) {
+ continue;
+ } else if (propValue === true) {
+ propValue = '';
+ } else if (typeof propValue === 'object') {
+ continue;
+ }
+ }
target.push(
attributeSeparator,
- stringToChunk(propKey),
+ stringToChunk(attributeName),
attributeAssign,
stringToChunk(escapeTextForBrowser(propValue)),
attributeEnd,
diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
index a873a257528c5..5d9c8bcaeccc4 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
@@ -3624,6 +3624,32 @@ describe('ReactDOMFizzServer', () => {
);
});
+ // bugfix: https://github.com/facebook/react/issues/27286 affecting enableCustomElementPropertySupport flag
+ it('can render custom elements with children on ther server', async () => {
+ await act(() => {
+ renderToPipeableStream(
+
+
+
+ foo
+
+
+ ,
+ ).pipe(writable);
+ });
+
+ expect(getVisibleChildren(document)).toEqual(
+
+
+
+
+ foo
+
+
+ ,
+ );
+ });
+
describe('error escaping', () => {
it('escapes error hash, message, and component stack values in directly flushed errors (html escaping)', async () => {
window.__outlet = {};