From ce4f33893c4233159ec7d280c6ff764d9ea6e74d Mon Sep 17 00:00:00 2001 From: ShawnCrawley-NOAA Date: Fri, 16 Jan 2026 09:33:12 -0700 Subject: [PATCH 1/2] Handle empty tagName by using Fragment Addresses https://github.com/reactive-python/reactpy/discussions/1323. I spent hours digging this bug down. Without this little change, it was impossible to nest a custom reactpy component (i.e. a function wrapped with the component decorator). It would yield an error along the lines of "Cannot create element with --- src/js/packages/@reactpy/client/src/vdom.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/packages/@reactpy/client/src/vdom.tsx b/src/js/packages/@reactpy/client/src/vdom.tsx index e57ea4594..c7d423a04 100644 --- a/src/js/packages/@reactpy/client/src/vdom.tsx +++ b/src/js/packages/@reactpy/client/src/vdom.tsx @@ -93,7 +93,7 @@ function createImportSourceElement(props: { } } } else { - type = props.model.tagName; + type = props.model.tagName === "" ? Fragment : props.model.tagName; } return props.binding.create( type, From abbdb2cea529d19cc27fd7e93fe91282dbabbf38 Mon Sep 17 00:00:00 2001 From: ShawnCrawley-NOAA Date: Fri, 16 Jan 2026 10:02:38 -0700 Subject: [PATCH 2/2] Adds test for bugfix --- .../js_fixtures/nest-custom-under-web.js | 14 +++++++++ .../js_fixtures/nest-custom-under-web.js | 14 +++++++++ tests/test_web/test_module.py | 29 +++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 tests/test_reactjs/js_fixtures/nest-custom-under-web.js create mode 100644 tests/test_web/js_fixtures/nest-custom-under-web.js diff --git a/tests/test_reactjs/js_fixtures/nest-custom-under-web.js b/tests/test_reactjs/js_fixtures/nest-custom-under-web.js new file mode 100644 index 000000000..027c9a2b7 --- /dev/null +++ b/tests/test_reactjs/js_fixtures/nest-custom-under-web.js @@ -0,0 +1,14 @@ +import React from "https://esm.sh/react@19.0" +import ReactDOM from "https://esm.sh/react-dom@19.0/client" +import {Container} from "https://esm.sh/react-bootstrap@2.10.10/?deps=react@19.0,react-dom@19.0,react-is@19.0&exports=Container"; +export {Container}; + +export function bind(node, config) { + const root = ReactDOM.createRoot(node); + return { + create: (type, props, children) => + React.createElement(type, props, children), + render: (element) => root.render(element, node), + unmount: () => root.unmount() + }; +} \ No newline at end of file diff --git a/tests/test_web/js_fixtures/nest-custom-under-web.js b/tests/test_web/js_fixtures/nest-custom-under-web.js new file mode 100644 index 000000000..027c9a2b7 --- /dev/null +++ b/tests/test_web/js_fixtures/nest-custom-under-web.js @@ -0,0 +1,14 @@ +import React from "https://esm.sh/react@19.0" +import ReactDOM from "https://esm.sh/react-dom@19.0/client" +import {Container} from "https://esm.sh/react-bootstrap@2.10.10/?deps=react@19.0,react-dom@19.0,react-is@19.0&exports=Container"; +export {Container}; + +export function bind(node, config) { + const root = ReactDOM.createRoot(node); + return { + create: (type, props, children) => + React.createElement(type, props, children), + render: (element) => root.render(element, node), + unmount: () => root.unmount() + }; +} \ No newline at end of file diff --git a/tests/test_web/test_module.py b/tests/test_web/test_module.py index cddcc86dc..c8ec22db7 100644 --- a/tests/test_web/test_module.py +++ b/tests/test_web/test_module.py @@ -563,3 +563,32 @@ async def test_module_without_bind(display: DisplayFixture): "#my-generic-component", state="attached" ) assert await element.inner_text() == "Hello World" + +async def test_nest_custom_component_under_web_component(display: DisplayFixture): + """ + Fix https://github.com/reactive-python/reactpy/discussions/1323 + + Custom components (i.e those wrapped in the component decorator) were not able to + be nested under web components. + + """ + module = reactpy.reactjs.file_to_module( + "nest-custom-under-web", JS_FIXTURES_DIR / "nest-custom-under-web.js" + ) + Container = reactpy.reactjs.module_to_vdom(module, "Container") + + @reactpy.component + def CustomComponent(): + return reactpy.html.div( + reactpy.html.h1({"id": "my-header"}, "Header 1") + ) + await display.show( + lambda: Container( + CustomComponent() + ) + ) + + element = await display.page.wait_for_selector( + "#my-header", state="attached" + ) + assert await element.inner_text() == "Header 1"