From ce4f33893c4233159ec7d280c6ff764d9ea6e74d Mon Sep 17 00:00:00 2001 From: ShawnCrawley-NOAA Date: Fri, 16 Jan 2026 09:33:12 -0700 Subject: [PATCH 1/3] 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/3] 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" From dde4eeeca2662be394406019005ff0304f13fcca Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 24 Jan 2026 14:47:55 -0800 Subject: [PATCH 3/3] Refactor and fix lint --- src/js/packages/@reactpy/client/src/vdom.tsx | 1 + tests/test_reactjs/test_modules.py | 25 +++++++++++++ tests/test_web/__init__.py | 4 +++ .../js_fixtures/nest-custom-under-web.js | 14 -------- tests/test_web/test_module.py | 35 +++---------------- 5 files changed, 34 insertions(+), 45 deletions(-) delete mode 100644 tests/test_web/js_fixtures/nest-custom-under-web.js diff --git a/src/js/packages/@reactpy/client/src/vdom.tsx b/src/js/packages/@reactpy/client/src/vdom.tsx index c7d423a04..8b289fceb 100644 --- a/src/js/packages/@reactpy/client/src/vdom.tsx +++ b/src/js/packages/@reactpy/client/src/vdom.tsx @@ -1,4 +1,5 @@ import eventToObject from "event-to-object"; +import { Fragment } from "preact"; import type { ReactPyVdom, ReactPyVdomImportSource, diff --git a/tests/test_reactjs/test_modules.py b/tests/test_reactjs/test_modules.py index 8b3992ef0..7a458defa 100644 --- a/tests/test_reactjs/test_modules.py +++ b/tests/test_reactjs/test_modules.py @@ -1,3 +1,5 @@ +from pathlib import Path + import pytest import reactpy @@ -5,6 +7,8 @@ from reactpy.reactjs import component_from_string, import_reactjs from reactpy.testing import BackendFixture, DisplayFixture +JS_FIXTURES_DIR = Path(__file__).parent / "js_fixtures" + @pytest.fixture(scope="module") async def display(browser): @@ -89,3 +93,24 @@ def App(): elements = await display.page.query_selector_all(".component-c") assert len(elements) == 2 await display.page.wait_for_selector("#deep-server") + + +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. + """ + Container = reactpy.reactjs.component_from_file( + JS_FIXTURES_DIR / "nest-custom-under-web.js", "Container", name="nest-custom-under-web" + ) + + @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" diff --git a/tests/test_web/__init__.py b/tests/test_web/__init__.py index e69de29bb..6a5447f43 100644 --- a/tests/test_web/__init__.py +++ b/tests/test_web/__init__.py @@ -0,0 +1,4 @@ +""" +THESE ARE TESTS FOR THE LEGACY API. SEE tests/test_reactjs/* FOR THE NEW API TESTS. +THE CONTENTS OF THIS MODULE WILL BE MIGRATED OR DELETED ONCE THE LEGACY API IS REMOVED. +""" diff --git a/tests/test_web/js_fixtures/nest-custom-under-web.js b/tests/test_web/js_fixtures/nest-custom-under-web.js deleted file mode 100644 index 027c9a2b7..000000000 --- a/tests/test_web/js_fixtures/nest-custom-under-web.js +++ /dev/null @@ -1,14 +0,0 @@ -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 c8ec22db7..34d857b7f 100644 --- a/tests/test_web/test_module.py +++ b/tests/test_web/test_module.py @@ -1,5 +1,7 @@ -# THESE ARE TESTS FOR THE LEGACY API. SEE tests/test_reactjs/* FOR THE NEW API TESTS. -# THIS MODULE WILL BE MIGRATED OR DELETED ONCE THE LEGACY API IS REMOVED. +""" +THESE ARE TESTS FOR THE LEGACY API. SEE tests/test_reactjs/* FOR THE NEW API TESTS. +THE CONTENTS OF THIS MODULE WILL BE MIGRATED OR DELETED ONCE THE LEGACY API IS REMOVED. +""" from pathlib import Path import pytest @@ -563,32 +565,3 @@ 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"