From 35c7e8dae5af44a22458733134fd5d6663004a86 Mon Sep 17 00:00:00 2001
From: ShawnCrawley-NOAA <shawn.crawley@noaa.gov>
Date: Mon, 10 Feb 2025 08:25:01 -0700
Subject: [PATCH 01/14] Ensures key is properly passed to importedElements

The "key" prop was being lost in the trickle-down of importedElements.
---
 src/js/packages/@reactpy/client/src/reactpy-vdom.tsx | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx b/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx
index 22fa3e61d..7d7784585 100644
--- a/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx
+++ b/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx
@@ -133,6 +133,14 @@ export function createAttributes(
   model: ReactPyVdom,
   client: ReactPyClient,
 ): { [key: string]: any } {
+  // Add key to attributes
+  if (model.key) {
+    if (model.attributes) {
+      model.attributes['key'] = model.key
+    } else {
+      model.attributes = {'key': model.key}
+    }
+  }
   return Object.fromEntries(
     Object.entries({
       // Normal HTML attributes

From fc6c001e88aead1a5611d22b8d0684bb3b38cd9b Mon Sep 17 00:00:00 2001
From: ShawnCrawley-NOAA <shawn.crawley@noaa.gov>
Date: Mon, 10 Feb 2025 08:45:10 -0700
Subject: [PATCH 02/14] Add semicolons

---
 src/js/packages/@reactpy/client/src/reactpy-vdom.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx b/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx
index 7d7784585..fa27adf43 100644
--- a/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx
+++ b/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx
@@ -136,9 +136,9 @@ export function createAttributes(
   // Add key to attributes
   if (model.key) {
     if (model.attributes) {
-      model.attributes['key'] = model.key
+      model.attributes['key'] = model.key;
     } else {
-      model.attributes = {'key': model.key}
+      model.attributes = {'key': model.key};
     }
   }
   return Object.fromEntries(

From 22a1de898633a124cba2e35fc3d7b4f014be208e Mon Sep 17 00:00:00 2001
From: ShawnCrawley-NOAA <shawn.crawley@noaa.gov>
Date: Mon, 10 Feb 2025 09:52:25 -0700
Subject: [PATCH 03/14] Update reactpy-vdom.tsx

---
 src/js/packages/@reactpy/client/src/reactpy-vdom.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx b/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx
index fa27adf43..9aff47b13 100644
--- a/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx
+++ b/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx
@@ -136,9 +136,9 @@ export function createAttributes(
   // Add key to attributes
   if (model.key) {
     if (model.attributes) {
-      model.attributes['key'] = model.key;
+      model.attributes.key = model.key;
     } else {
-      model.attributes = {'key': model.key};
+      model.attributes = {key: model.key};
     }
   }
   return Object.fromEntries(

From a41adbee11cf385ffa4d4cf69470279b5fdce004 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Tue, 11 Feb 2025 11:21:48 -0800
Subject: [PATCH 04/14] Format via `hatch run javascript:fix`

---
 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 5c7f8e306..a8a324b4a 100644
--- a/src/js/packages/@reactpy/client/src/vdom.tsx
+++ b/src/js/packages/@reactpy/client/src/vdom.tsx
@@ -147,7 +147,7 @@ export function createAttributes(
     if (model.attributes) {
       model.attributes.key = model.key;
     } else {
-      model.attributes = {key: model.key};
+      model.attributes = { key: model.key };
     }
   }
   return Object.fromEntries(

From b1e01cc24dffec8d6b1bac11028d0c22f6344741 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Tue, 11 Feb 2025 11:31:15 -0800
Subject: [PATCH 05/14] Add changelog item

---
 docs/source/about/changelog.rst | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/docs/source/about/changelog.rst b/docs/source/about/changelog.rst
index c90a8dcff..c361113da 100644
--- a/docs/source/about/changelog.rst
+++ b/docs/source/about/changelog.rst
@@ -16,6 +16,7 @@ Unreleased
 ----------
 
 **Added**
+
 - :pull:`1113` - Added ``reactpy.executors.asgi.ReactPy`` that can be used to run ReactPy in standalone mode via ASGI.
 - :pull:`1269` - Added ``reactpy.executors.asgi.ReactPyPyodide`` that can be used to run ReactPy in standalone mode via ASGI, but rendered entirely client-sided.
 - :pull:`1113` - Added ``reactpy.executors.asgi.ReactPyMiddleware`` that can be used to utilize ReactPy within any ASGI compatible framework.
@@ -56,6 +57,7 @@ Unreleased
 **Fixed**
 
 - :pull:`1239` - Fixed a bug where script elements would not render to the DOM as plain text.
+- :pull:`1271` - Fixed bug where JavaScript components were unable to obtain the ``key`` attribute provided within ReactPy.
 
 v1.1.0
 ------

From e1ff1ce85eb3cf27a37bb878fefb5ddbc1aa72bb Mon Sep 17 00:00:00 2001
From: Shawn Crawley <shawncrawley@gmail.com>
Date: Mon, 10 Mar 2025 10:13:08 -0600
Subject: [PATCH 06/14] Test written

---
 .../js_fixtures/keys-properly-propagated.js   | 15 ++++++
 tests/test_web/test_module.py                 | 54 +++++++++++++++++++
 2 files changed, 69 insertions(+)
 create mode 100644 tests/test_web/js_fixtures/keys-properly-propagated.js

diff --git a/tests/test_web/js_fixtures/keys-properly-propagated.js b/tests/test_web/js_fixtures/keys-properly-propagated.js
new file mode 100644
index 000000000..d425a74fa
--- /dev/null
+++ b/tests/test_web/js_fixtures/keys-properly-propagated.js
@@ -0,0 +1,15 @@
+import React from "https://esm.sh/react@19.0"
+import ReactDOM from "https://esm.sh/react-dom@19.0/client"
+import GridLayout from "https://esm.sh/react-grid-layout@1.5.0";
+export {GridLayout};
+
+export function bind(node, config) {
+  debugger;
+  const root = ReactDOM.createRoot(node);
+  return {
+    create: (type, props, children) => 
+      React.createElement(type, props, children),
+    render: (element) => root.render(element, node),
+    unmount: () => root.unmount()
+  };
+}
diff --git a/tests/test_web/test_module.py b/tests/test_web/test_module.py
index 8cd487c0c..22246cedc 100644
--- a/tests/test_web/test_module.py
+++ b/tests/test_web/test_module.py
@@ -208,6 +208,60 @@ async def test_imported_components_can_render_children(display: DisplayFixture):
         assert (await child.get_attribute("id")) == f"child-{index + 1}"
 
 
+async def test_keys_properly_propagated(display: DisplayFixture):
+    module = reactpy.web.module_from_file(
+        "keys-properly-propagated", JS_FIXTURES_DIR / "keys-properly-propagated.js"
+    )
+    GridLayout = reactpy.web.export(module, "GridLayout")
+
+    await display.show(
+        lambda: GridLayout({
+            "layout": [
+                {
+                    "i": "a",
+                    "x": 0,
+                    "y": 0,
+                    "w": 1,
+                    "h": 2,
+                    "static": True,
+                },
+                {
+                    "i": "b",
+                    "x": 1,
+                    "y": 0,
+                    "w": 3,
+                    "h": 2,
+                    "minW": 2,
+                    "maxW": 4,
+                },
+                {
+                    "i": "c",
+                    "x": 4,
+                    "y": 0,
+                    "w": 1,
+                    "h": 2,
+                }
+            ],
+            "cols": 12,
+            "rowHeight": 30,
+            "width": 1200,
+        },
+            reactpy.html.div({"key": "a"}, "a"),
+            reactpy.html.div({"key": "b"}, "b"),
+            reactpy.html.div({"key": "c"}, "c"),
+        )
+    )
+
+    parent = await display.page.wait_for_selector(".react-grid-layout", state="attached", timeout=0)
+    children = await parent.query_selector_all("div")
+
+    # The children simply will not render unless they receive the key prop
+    assert len(children) == 3
+
+    # for index, child in enumerate(children):
+    #     assert (await child.get_attribute("key")) == child.get_text()
+
+
 def test_module_from_string():
     reactpy.web.module_from_string("temp", "old")
     with assert_reactpy_did_log(r"Existing web module .* will be replaced with"):

From 1cc35027f95aba30122090309cf72b84ab31ff17 Mon Sep 17 00:00:00 2001
From: ShawnCrawley-NOAA <shawn.crawley@noaa.gov>
Date: Mon, 10 Mar 2025 10:24:58 -0600
Subject: [PATCH 07/14] Delete unnecessary, commented out lines

---
 tests/test_web/test_module.py | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/tests/test_web/test_module.py b/tests/test_web/test_module.py
index 22246cedc..d3474bad4 100644
--- a/tests/test_web/test_module.py
+++ b/tests/test_web/test_module.py
@@ -258,9 +258,6 @@ async def test_keys_properly_propagated(display: DisplayFixture):
     # The children simply will not render unless they receive the key prop
     assert len(children) == 3
 
-    # for index, child in enumerate(children):
-    #     assert (await child.get_attribute("key")) == child.get_text()
-
 
 def test_module_from_string():
     reactpy.web.module_from_string("temp", "old")

From 7a7dca266f419b40e37af7b6151af88ae7bba42e Mon Sep 17 00:00:00 2001
From: ShawnCrawley-NOAA <shawn.crawley@noaa.gov>
Date: Mon, 10 Mar 2025 10:53:29 -0600
Subject: [PATCH 08/14] Update keys-properly-propagated.js

Removes debugger statement.
---
 tests/test_web/js_fixtures/keys-properly-propagated.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/tests/test_web/js_fixtures/keys-properly-propagated.js b/tests/test_web/js_fixtures/keys-properly-propagated.js
index d425a74fa..8d700397e 100644
--- a/tests/test_web/js_fixtures/keys-properly-propagated.js
+++ b/tests/test_web/js_fixtures/keys-properly-propagated.js
@@ -4,7 +4,6 @@ import GridLayout from "https://esm.sh/react-grid-layout@1.5.0";
 export {GridLayout};
 
 export function bind(node, config) {
-  debugger;
   const root = ReactDOM.createRoot(node);
   return {
     create: (type, props, children) => 

From 60345a5565a4a79298f0be1b2114f965217c38c8 Mon Sep 17 00:00:00 2001
From: ShawnCrawley-NOAA <shawn.crawley@noaa.gov>
Date: Mon, 10 Mar 2025 10:55:24 -0600
Subject: [PATCH 09/14] Update test_module.py

Removes `timeout=0` that was there for testing
---
 tests/test_web/test_module.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/test_web/test_module.py b/tests/test_web/test_module.py
index d3474bad4..76ff620ce 100644
--- a/tests/test_web/test_module.py
+++ b/tests/test_web/test_module.py
@@ -252,7 +252,7 @@ async def test_keys_properly_propagated(display: DisplayFixture):
         )
     )
 
-    parent = await display.page.wait_for_selector(".react-grid-layout", state="attached", timeout=0)
+    parent = await display.page.wait_for_selector(".react-grid-layout", state="attached")
     children = await parent.query_selector_all("div")
 
     # The children simply will not render unless they receive the key prop

From b9f50da501adcc49a0c8dc11653ab85c1136c4ec Mon Sep 17 00:00:00 2001
From: Shawn Crawley <shawncrawley@gmail.com>
Date: Mon, 10 Mar 2025 21:10:22 -0600
Subject: [PATCH 10/14] Adds docstring and tweaks changelog Per feedback by
 @Archmonger

---
 docs/source/about/changelog.rst | 2 +-
 tests/test_web/test_module.py   | 7 +++++++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/docs/source/about/changelog.rst b/docs/source/about/changelog.rst
index d4aeda42b..0db168ba2 100644
--- a/docs/source/about/changelog.rst
+++ b/docs/source/about/changelog.rst
@@ -70,7 +70,7 @@ Unreleased
 **Fixed**
 
 - :pull:`1239` - Fixed a bug where script elements would not render to the DOM as plain text.
-- :pull:`1271` - Fixed bug where JavaScript components were unable to obtain the ``key`` attribute provided within ReactPy.
+- :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.
 - :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.
 
 v1.1.0
diff --git a/tests/test_web/test_module.py b/tests/test_web/test_module.py
index 76ff620ce..fc65250ba 100644
--- a/tests/test_web/test_module.py
+++ b/tests/test_web/test_module.py
@@ -209,6 +209,13 @@ async def test_imported_components_can_render_children(display: DisplayFixture):
 
 
 async def test_keys_properly_propagated(display: DisplayFixture):
+    """ Addresses issue #1271
+    The "key" property was being lost in its propagation from
+    the server-side ReactPy definition to the front-end JavaScript.
+    This is a critical issue for the proper functionality of
+    certain components, such as the GridLayout from 
+    react-grid-layout.
+    """
     module = reactpy.web.module_from_file(
         "keys-properly-propagated", JS_FIXTURES_DIR / "keys-properly-propagated.js"
     )

From 4ab15d4769067845fdcfe1b5f22a5194a69c6f52 Mon Sep 17 00:00:00 2001
From: Mark Bakhit <archiethemonger@gmail.com>
Date: Mon, 10 Mar 2025 20:43:26 -0700
Subject: [PATCH 11/14] Update docstring in test_module.py

---
 tests/test_web/test_module.py | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/tests/test_web/test_module.py b/tests/test_web/test_module.py
index fc65250ba..4b5f980c4 100644
--- a/tests/test_web/test_module.py
+++ b/tests/test_web/test_module.py
@@ -209,11 +209,13 @@ async def test_imported_components_can_render_children(display: DisplayFixture):
 
 
 async def test_keys_properly_propagated(display: DisplayFixture):
-    """ Addresses issue #1271
-    The "key" property was being lost in its propagation from
-    the server-side ReactPy definition to the front-end JavaScript.
-    This is a critical issue for the proper functionality of
-    certain components, such as the GridLayout from 
+    """
+    Fix https://github.com/reactive-python/reactpy/issues/1275
+
+    The `key` property was being lost in its propagation from the server-side ReactPy
+    definition to the front-end JavaScript.
+    
+    This property is required for certain JS components, such as the GridLayout from 
     react-grid-layout.
     """
     module = reactpy.web.module_from_file(

From 35f9e7d3587949ee7cfb0ff906b968c5484c4a49 Mon Sep 17 00:00:00 2001
From: Shawn Crawley <shawncrawley@gmail.com>
Date: Tue, 11 Mar 2025 06:42:17 -0600
Subject: [PATCH 12/14] Handles fix on server-side rather than client-side

---
 src/js/packages/@reactpy/client/src/vdom.tsx | 8 --------
 src/reactpy/core/vdom.py                     | 2 +-
 2 files changed, 1 insertion(+), 9 deletions(-)

diff --git a/src/js/packages/@reactpy/client/src/vdom.tsx b/src/js/packages/@reactpy/client/src/vdom.tsx
index a8a324b4a..25eb9f3e7 100644
--- a/src/js/packages/@reactpy/client/src/vdom.tsx
+++ b/src/js/packages/@reactpy/client/src/vdom.tsx
@@ -142,14 +142,6 @@ export function createAttributes(
   model: ReactPyVdom,
   client: ReactPyClientInterface,
 ): { [key: string]: any } {
-  // Add key to attributes
-  if (model.key) {
-    if (model.attributes) {
-      model.attributes.key = model.key;
-    } else {
-      model.attributes = { key: model.key };
-    }
-  }
   return Object.fromEntries(
     Object.entries({
       // Normal HTML attributes
diff --git a/src/reactpy/core/vdom.py b/src/reactpy/core/vdom.py
index 4186ab5a6..6bc28dfd4 100644
--- a/src/reactpy/core/vdom.py
+++ b/src/reactpy/core/vdom.py
@@ -148,7 +148,7 @@ def __call__(
     ) -> VdomDict:
         """The entry point for the VDOM API, for example reactpy.html(<WE_ARE_HERE>)."""
         attributes, children = separate_attributes_and_children(attributes_and_children)
-        key = attributes.pop("key", None)
+        key = attributes.get("key", None)
         attributes, event_handlers = separate_attributes_and_event_handlers(attributes)
         if REACTPY_CHECK_JSON_ATTRS.current:
             json.dumps(attributes)

From 7591d1fe7f39499442375f75cebd5b71342e1285 Mon Sep 17 00:00:00 2001
From: Shawn Crawley <shawncrawley@gmail.com>
Date: Tue, 11 Mar 2025 08:40:13 -0600
Subject: [PATCH 13/14] Update tests per maintaing "key" in vdom attrs

---
 src/reactpy/_html.py      | 2 +-
 src/reactpy/transforms.py | 2 +-
 tests/test_utils.py       | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/reactpy/_html.py b/src/reactpy/_html.py
index 9f160b403..6e9e331f9 100644
--- a/src/reactpy/_html.py
+++ b/src/reactpy/_html.py
@@ -104,7 +104,7 @@ def _fragment(
     event_handlers: EventHandlerDict,
 ) -> VdomDict:
     """An HTML fragment - this element will not appear in the DOM"""
-    if attributes or event_handlers:
+    if (attributes and any(a != "key" for a in attributes)) or event_handlers:
         msg = "Fragments cannot have attributes besides 'key'"
         raise TypeError(msg)
     model = VdomDict(tagName="")
diff --git a/src/reactpy/transforms.py b/src/reactpy/transforms.py
index 027fb3e3b..cdac48c7e 100644
--- a/src/reactpy/transforms.py
+++ b/src/reactpy/transforms.py
@@ -103,7 +103,7 @@ def infer_key_from_attributes(vdom: VdomDict) -> None:
             return
 
         # Infer 'key' from 'attributes.key'
-        key = attributes.pop("key", None)
+        key = attributes.get("key", None)
 
         # Infer 'key' from 'attributes.id'
         if key is None:
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 7e334dda5..e494c29b3 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -188,7 +188,7 @@ def test_string_to_reactpy(case):
         # 8: Infer ReactJS `key` from the `key` attribute
         {
             "source": '<div key="my-key"></div>',
-            "model": {"tagName": "div", "key": "my-key"},
+            "model": {"tagName": "div", "attributes": {"key": "my-key"}, "key": "my-key"},
         },
     ],
 )
@@ -253,7 +253,7 @@ def test_non_html_tag_behavior():
         "tagName": "my-tag",
         "attributes": {"data-x": "something"},
         "children": [
-            {"tagName": "my-other-tag", "key": "a-key"},
+            {"tagName": "my-other-tag", "attributes": {"key": "a-key"}, "key": "a-key"},
         ],
     }
 

From d457f61e6b8f39630eb45ff6389ed2e773009620 Mon Sep 17 00:00:00 2001
From: ShawnCrawley-NOAA <shawn.crawley@noaa.gov>
Date: Wed, 12 Mar 2025 07:11:20 -0600
Subject: [PATCH 14/14] Alternate solution for expensive any on attributes

Since "key" is the only allowed attribute, I just pop that out before checking if anything is left in attributes, and if so, the error is raised.
---
 src/reactpy/_html.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/reactpy/_html.py b/src/reactpy/_html.py
index 6e9e331f9..ffeee7072 100644
--- a/src/reactpy/_html.py
+++ b/src/reactpy/_html.py
@@ -104,7 +104,8 @@ def _fragment(
     event_handlers: EventHandlerDict,
 ) -> VdomDict:
     """An HTML fragment - this element will not appear in the DOM"""
-    if (attributes and any(a != "key" for a in attributes)) or event_handlers:
+    attributes.pop("key", None)
+    if attributes or event_handlers:
         msg = "Fragments cannot have attributes besides 'key'"
         raise TypeError(msg)
     model = VdomDict(tagName="")