@samitouri / QOS-React-1 / commits / bb778528d1

[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: https://github.com/facebook/react/issues/27286

Josh Story committed Oct 12, 2023 at 15:02 UTC bb778528d1ca22b44dad832f0258aaa4c0e6d4a4
2 files changed +45 -22
packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
+19 -22
@@ -3134,32 +3134,13 @@ function pushStartCustomElement(
3134
3135 let children = null;
3136 let innerHTML = null;
3137 - for (let propKey in props) {
3137 + for (const propKey in props) {
3138 if (hasOwnProperty.call(props, propKey)) {
3139 let propValue = props[propKey];
3140 if (propValue == null) {
3141 continue;
3142 }
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;
3144 switch (propKey) {
3145 case 'children':
3146 children = propValue;
@@ -3174,15 +3155,31 @@ function pushStartCustomElement(
3155 case 'suppressHydrationWarning':
3156 // Ignored. These are built-in to React on the client.
3157 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
3165 default:
3166 if (
3167 isAttributeNameSafe(propKey) &&
3168 typeof propValue !== 'function' &&
3169 typeof propValue !== 'symbol'
3170 ) {
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 + }
3180 target.push(
3181 attributeSeparator,
3185 - stringToChunk(propKey),
3182 + stringToChunk(attributeName),
3183 attributeAssign,
3184 stringToChunk(escapeTextForBrowser(propValue)),
3185 attributeEnd,
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+26
@@ -3624,6 +3624,32 @@ describe('ReactDOMFizzServer', () => {
3624 );
3625 });
3626
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 +
3653 describe('error escaping', () => {
3654 it('escapes error hash, message, and component stack values in directly flushed errors (html escaping)', async () => {
3655 window.__outlet = {};