Skip to content

Commit 11303f8

Browse files
committed
Addresses feedback by @Archmonger
1 parent b3063ba commit 11303f8

File tree

4 files changed

+53
-25
lines changed

4 files changed

+53
-25
lines changed

docs/source/about/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Unreleased
2929
- :pull:`1281` - ``reactpy.html`` will now automatically flatten lists recursively (ex. ``reactpy.html(["child1", ["child2"]])``)
3030
- :pull:`1281` - Added ``reactpy.Vdom`` primitive interface for creating VDOM dictionaries.
3131
- :pull:`1281` - Added type hints to ``reactpy.html`` attributes.
32+
- :pull:`1285` - Added support for nested components in web modules
3233

3334
**Changed**
3435

src/js/packages/@reactpy/client/src/vdom.tsx

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ function createImportSourceElement(props: {
6868
}): any {
6969
let type: any;
7070
if (props.model.importSource) {
71-
const rootType = props.model.tagName.split(".")[0];
7271
if (
7372
!isImportSourceEqual(props.currentImportSource, props.model.importSource)
7473
) {
@@ -79,16 +78,16 @@ function createImportSourceElement(props: {
7978
stringifyImportSource(props.model.importSource),
8079
);
8180
return null;
82-
} else if (!props.module[rootType]) {
83-
log.error(
84-
"Module from source " +
85-
stringifyImportSource(props.currentImportSource) +
86-
` does not export ${rootType}`,
87-
);
88-
return null;
8981
} else {
90-
type = tryGetSubType(props.module, props.model.tagName);
91-
if (!type) return null;
82+
type = getComponentFromModule(
83+
props.module,
84+
props.model.tagName,
85+
props.model.importSource,
86+
);
87+
if (!type) {
88+
// Error message logged within getComponentFromModule
89+
return null;
90+
}
9291
}
9392
} else {
9493
type = props.model.tagName;
@@ -105,25 +104,40 @@ function createImportSourceElement(props: {
105104
);
106105
}
107106

108-
function tryGetSubType(module: ReactPyModule, component: string) {
109-
let subComponents: string[] = component.split(".");
110-
const rootComponent: string = subComponents[0];
111-
let subComponentAccessor: string = rootComponent;
112-
let type: any = module[rootComponent];
107+
function getComponentFromModule(
108+
module: ReactPyModule,
109+
componentName: string,
110+
importSource: ReactPyVdomImportSource,
111+
): any {
112+
/* Gets the component with the provided name from the provided module.
113113
114-
subComponents = subComponents.slice(1);
115-
for (let i = 0; i < subComponents.length; i++) {
116-
const subComponent = subComponents[i];
117-
subComponentAccessor += "." + subComponent;
118-
type = type[subComponent];
119-
if (!type) {
120-
console.error(
121-
`Component ${rootComponent} does not have subcomponent ${subComponentAccessor}`,
122-
);
114+
Built specifically to work on inifinitely deep nested components.
115+
For example, component "My.Nested.Component" is accessed from
116+
ModuleA like so: ModuleA["My"]["Nested"]["Component"].
117+
*/
118+
const componentParts: string[] = componentName.split(".");
119+
let Component: any = null;
120+
for (let i = 0; i < componentParts.length; i++) {
121+
const iterAttr = componentParts[i];
122+
Component = i == 0 ? module[iterAttr] : Component[iterAttr];
123+
if (!Component) {
124+
if (i == 0) {
125+
log.error(
126+
"Module from source " +
127+
stringifyImportSource(importSource) +
128+
` does not export ${iterAttr}`,
129+
);
130+
} else {
131+
console.error(
132+
`Component ${componentParts.slice(0, i).join(".")} from source ` +
133+
stringifyImportSource(importSource) +
134+
` does not have subcomponent ${iterAttr}`,
135+
);
136+
}
123137
break;
124138
}
125139
}
126-
return type;
140+
return Component;
127141
}
128142

129143
function isImportSourceEqual(

src/reactpy/core/vdom.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ def __init__(
136136
self.__qualname__ = f"{module_name}.{tag_name}"
137137

138138
def __getattr__(self, attr: str) -> Vdom:
139+
"""Supports accessing nested web module components"""
140+
if not self.import_source:
141+
msg = "Nested comopnents can only be accessed on web module components."
142+
raise AttributeError(msg)
139143
return Vdom(
140144
f"{self.__name__}.{attr}",
141145
allow_children=self.allow_children,

tests/test_core/test_vdom.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,15 @@ def test_make_vdom_constructor():
123123
assert no_children() == {"tagName": "no-children"}
124124

125125

126+
def test_nested_html_access_raises_error():
127+
elmt = Vdom("div")
128+
129+
with pytest.raises(
130+
AttributeError, match="can only be accessed on web module components"
131+
):
132+
elmt.fails()
133+
134+
126135
@pytest.mark.parametrize(
127136
"value",
128137
[

0 commit comments

Comments
 (0)