diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponent.js b/packages/react-dom-bindings/src/client/ReactDOMComponent.js index 80ee96a87a8f..919c9caab111 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMComponent.js +++ b/packages/react-dom-bindings/src/client/ReactDOMComponent.js @@ -736,6 +736,20 @@ function setProp( } break; } + case 'hidden': { + if (value === 'until-found') { + domElement.setAttribute('hidden', 'until-found'); + } else if ( + value && + typeof value !== 'function' && + typeof value !== 'symbol' + ) { + domElement.setAttribute(key, ''); + } else { + domElement.removeAttribute(key); + } + break; + } // Boolean case 'inert': { if (__DEV__) { @@ -763,7 +777,6 @@ function setProp( case 'disablePictureInPicture': case 'disableRemotePlayback': case 'formNoValidate': - case 'hidden': case 'loop': case 'noModule': case 'noValidate': diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index db3e2806ac3e..fedbc87a22d1 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -156,7 +156,7 @@ export type Props = { autoFocus?: boolean, children?: mixed, disabled?: boolean, - hidden?: boolean, + hidden?: boolean | 'until-found', suppressHydrationWarning?: boolean, dangerouslySetInnerHTML?: mixed, style?: { diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js index 691e49e563fd..63db66569802 100644 --- a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js +++ b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js @@ -1676,6 +1676,20 @@ function pushAttribute( } return; } + case 'hidden': { + if (value === 'until-found') { + target.push( + attributeSeparator, + stringToChunk(name), + attributeAssign, + stringToChunk('until-found'), + attributeEnd, + ); + return; + } + pushBooleanAttribute(target, name, value); + return; + } case 'inert': { if (__DEV__) { if (value === '' && !didWarnForNewBooleanPropsWithEmptyValue[name]) { @@ -1702,7 +1716,6 @@ function pushAttribute( case 'disablePictureInPicture': case 'disableRemotePlayback': case 'formNoValidate': - case 'hidden': case 'loop': case 'noModule': case 'noValidate': @@ -5854,7 +5867,7 @@ function writeStyleResourceAttributeInJS( if (value === false) { return; } - attributeValue = ''; + attributeValue = value === 'until-found' ? 'until-found' : ''; break; } // Santized URLs @@ -6049,7 +6062,7 @@ function writeStyleResourceAttributeInAttr( if (value === false) { return; } - attributeValue = ''; + attributeValue = value === 'until-found' ? 'until-found' : ''; break; } diff --git a/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js b/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js index 5bbc2c8dbac8..f43c0833c2ee 100644 --- a/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js +++ b/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js @@ -230,6 +230,19 @@ describe('DOMPropertyOperations', () => { expect(container.firstChild.hasAttribute('hidden')).toBe(false); }); + it('should preserve hidden until-found attribute values', async () => { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render(