Skip to content

Commit 9c32dc1

Browse files
authored
Ensures key is properly propagated to importedElements (#1271)
1 parent fa48bbc commit 9c32dc1

File tree

7 files changed

+81
-4
lines changed

7 files changed

+81
-4
lines changed

docs/source/about/changelog.rst

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Unreleased
1616
----------
1717

1818
**Added**
19+
1920
- :pull:`1113` - Added ``reactpy.executors.asgi.ReactPy`` that can be used to run ReactPy in standalone mode via ASGI.
2021
- :pull:`1269` - Added ``reactpy.executors.asgi.ReactPyPyodide`` that can be used to run ReactPy in standalone mode via ASGI, but rendered entirely client-sided.
2122
- :pull:`1113` - Added ``reactpy.executors.asgi.ReactPyMiddleware`` that can be used to utilize ReactPy within any ASGI compatible framework.
@@ -69,6 +70,7 @@ Unreleased
6970
**Fixed**
7071

7172
- :pull:`1239` - Fixed a bug where script elements would not render to the DOM as plain text.
73+
- :pull:`1271` - Fixed a bug where the ``key`` property provided via server-side ReactPy code was failing to propagate to the front-end JavaScript component.
7274
- :pull:`1254` - Fixed a bug where ``RuntimeError("Hook stack is in an invalid state")`` errors would be provided when using a webserver that reuses threads.
7375

7476
v1.1.0

src/reactpy/_html.py

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ def _fragment(
104104
event_handlers: EventHandlerDict,
105105
) -> VdomDict:
106106
"""An HTML fragment - this element will not appear in the DOM"""
107+
attributes.pop("key", None)
107108
if attributes or event_handlers:
108109
msg = "Fragments cannot have attributes besides 'key'"
109110
raise TypeError(msg)

src/reactpy/core/vdom.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def __call__(
148148
) -> VdomDict:
149149
"""The entry point for the VDOM API, for example reactpy.html(<WE_ARE_HERE>)."""
150150
attributes, children = separate_attributes_and_children(attributes_and_children)
151-
key = attributes.pop("key", None)
151+
key = attributes.get("key", None)
152152
attributes, event_handlers = separate_attributes_and_event_handlers(attributes)
153153
if REACTPY_CHECK_JSON_ATTRS.current:
154154
json.dumps(attributes)

src/reactpy/transforms.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def infer_key_from_attributes(vdom: VdomDict) -> None:
103103
return
104104

105105
# Infer 'key' from 'attributes.key'
106-
key = attributes.pop("key", None)
106+
key = attributes.get("key", None)
107107

108108
# Infer 'key' from 'attributes.id'
109109
if key is None:

tests/test_utils.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def test_string_to_reactpy(case):
188188
# 8: Infer ReactJS `key` from the `key` attribute
189189
{
190190
"source": '<div key="my-key"></div>',
191-
"model": {"tagName": "div", "key": "my-key"},
191+
"model": {"tagName": "div", "attributes": {"key": "my-key"}, "key": "my-key"},
192192
},
193193
],
194194
)
@@ -253,7 +253,7 @@ def test_non_html_tag_behavior():
253253
"tagName": "my-tag",
254254
"attributes": {"data-x": "something"},
255255
"children": [
256-
{"tagName": "my-other-tag", "key": "a-key"},
256+
{"tagName": "my-other-tag", "attributes": {"key": "a-key"}, "key": "a-key"},
257257
],
258258
}
259259

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React from "https://esm.sh/[email protected]"
2+
import ReactDOM from "https://esm.sh/[email protected]/client"
3+
import GridLayout from "https://esm.sh/[email protected]";
4+
export {GridLayout};
5+
6+
export function bind(node, config) {
7+
const root = ReactDOM.createRoot(node);
8+
return {
9+
create: (type, props, children) =>
10+
React.createElement(type, props, children),
11+
render: (element) => root.render(element, node),
12+
unmount: () => root.unmount()
13+
};
14+
}

tests/test_web/test_module.py

+60
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,66 @@ async def test_imported_components_can_render_children(display: DisplayFixture):
208208
assert (await child.get_attribute("id")) == f"child-{index + 1}"
209209

210210

211+
async def test_keys_properly_propagated(display: DisplayFixture):
212+
"""
213+
Fix https://github.com/reactive-python/reactpy/issues/1275
214+
215+
The `key` property was being lost in its propagation from the server-side ReactPy
216+
definition to the front-end JavaScript.
217+
218+
This property is required for certain JS components, such as the GridLayout from
219+
react-grid-layout.
220+
"""
221+
module = reactpy.web.module_from_file(
222+
"keys-properly-propagated", JS_FIXTURES_DIR / "keys-properly-propagated.js"
223+
)
224+
GridLayout = reactpy.web.export(module, "GridLayout")
225+
226+
await display.show(
227+
lambda: GridLayout({
228+
"layout": [
229+
{
230+
"i": "a",
231+
"x": 0,
232+
"y": 0,
233+
"w": 1,
234+
"h": 2,
235+
"static": True,
236+
},
237+
{
238+
"i": "b",
239+
"x": 1,
240+
"y": 0,
241+
"w": 3,
242+
"h": 2,
243+
"minW": 2,
244+
"maxW": 4,
245+
},
246+
{
247+
"i": "c",
248+
"x": 4,
249+
"y": 0,
250+
"w": 1,
251+
"h": 2,
252+
}
253+
],
254+
"cols": 12,
255+
"rowHeight": 30,
256+
"width": 1200,
257+
},
258+
reactpy.html.div({"key": "a"}, "a"),
259+
reactpy.html.div({"key": "b"}, "b"),
260+
reactpy.html.div({"key": "c"}, "c"),
261+
)
262+
)
263+
264+
parent = await display.page.wait_for_selector(".react-grid-layout", state="attached")
265+
children = await parent.query_selector_all("div")
266+
267+
# The children simply will not render unless they receive the key prop
268+
assert len(children) == 3
269+
270+
211271
def test_module_from_string():
212272
reactpy.web.module_from_string("temp", "old")
213273
with assert_reactpy_did_log(r"Existing web module .* will be replaced with"):

0 commit comments

Comments
 (0)