From b44e2feb720e73b3251a4e4a72043b6fa469ce2e Mon Sep 17 00:00:00 2001 From: Iisakki Rotko Date: Mon, 13 Jan 2025 16:12:16 +0100 Subject: [PATCH 1/4] feat: check for outdated props after first render If children or props were updated while the first render's async function resolutions were still settling, the change would not be reflected in the front-end until the next rerender. --- package-lock.json | 1 + package.json | 1 + src/widget.tsx | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/package-lock.json b/package-lock.json index e1007f3..66f1613 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "babel-plugin-import-map": "^1.0.0", "es-module-shims": "^2.0.0", "esbuild": "^0.17.14", + "lodash": "^4.17.21", "raw-loader": "^4.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/package.json b/package.json index 841b644..003488b 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "babel-plugin-import-map": "^1.0.0", "es-module-shims": "^2.0.0", "esbuild": "^0.17.14", + "lodash": "^4.17.21", "raw-loader": "^4.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/widget.tsx b/src/widget.tsx index a58c03a..2a74735 100644 --- a/src/widget.tsx +++ b/src/widget.tsx @@ -29,6 +29,7 @@ import { transform } from "sucrase"; import { ErrorBoundary, JupyterWidget } from "./components"; import { Root } from "react-dom/client"; import { ModelDestroyOptions } from "backbone"; +import { isEqual } from "lodash"; declare function importShim( specifier: string, @@ -728,6 +729,24 @@ export class ReactModel extends DOMWidgetModel { } this.listenTo(this, `change:${key}`, updateChildren); } + // If props or children were updated while we were initializing the view, + // we want to do a rerender + const checkPropsChange = async () => { + const [currentProps, currentChildren] = await Promise.all([ + replaceWidgetWithComponent(this.get("props"), get_model), + replaceWidgetWithComponent( + { children: this.get("children") }, + get_model, + ), + ]); + if (!isEqual(currentProps, initialModelProps)) { + updateModelProps(); + } + if (!isEqual(currentChildren, initialChildrenComponents)) { + updateChildren(); + } + }; + this.enqueue(checkPropsChange); return () => { this.stopListening(this, "change:props", updateModelProps); this.stopListening(this, "change:children", updateChildren); From 698c2247d378cb832137ab66a9869161f909133f Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Wed, 15 Jan 2025 14:58:32 +0100 Subject: [PATCH 2/4] test: test if children can be updated directly after creation --- tests/ui/children_test.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/ui/children_test.py b/tests/ui/children_test.py index b62fbe7..de1dab2 100644 --- a/tests/ui/children_test.py +++ b/tests/ui/children_test.py @@ -58,3 +58,15 @@ def on_click(event_data): button.click() # not per se a direct child page_session.locator(".test-button >> .test-html").wait_for() + + +def test_children_update_after_create(solara_test, page_session: playwright.sync_api.Page): + b = ipyreact.Widget( + _type="button", + children=["Initial"], + props={"class": "test-button"}, + ) + b.children = ["Updated"] + + display(b) + page_session.locator(".test-button >> text=Updated").wait_for() From 328166acf90f272cf3df5f14382f1726b8647031 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Wed, 15 Jan 2025 14:58:54 +0100 Subject: [PATCH 3/4] refactor: move file for better naming --- tests/ui/{children_test.py => basics_test.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/ui/{children_test.py => basics_test.py} (100%) diff --git a/tests/ui/children_test.py b/tests/ui/basics_test.py similarity index 100% rename from tests/ui/children_test.py rename to tests/ui/basics_test.py From 65ad8f2211ca25a529afb9bc08f81519d5aebbf3 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Wed, 15 Jan 2025 15:01:46 +0100 Subject: [PATCH 4/4] test: test if props can be updated directly after creation --- tests/ui/basics_test.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/ui/basics_test.py b/tests/ui/basics_test.py index de1dab2..8054a08 100644 --- a/tests/ui/basics_test.py +++ b/tests/ui/basics_test.py @@ -60,7 +60,7 @@ def on_click(event_data): page_session.locator(".test-button >> .test-html").wait_for() -def test_children_update_after_create(solara_test, page_session: playwright.sync_api.Page): +def test_update_children_after_create(solara_test, page_session: playwright.sync_api.Page): b = ipyreact.Widget( _type="button", children=["Initial"], @@ -70,3 +70,15 @@ def test_children_update_after_create(solara_test, page_session: playwright.sync display(b) page_session.locator(".test-button >> text=Updated").wait_for() + + +def test_update_props_after_create(solara_test, page_session: playwright.sync_api.Page): + b = ipyreact.Widget( + _type="button", + children=["Button"], + props={"class": "test-button-initial"}, + ) + b.props = {"class": "test-button-updated"} + + display(b) + page_session.locator(".test-button-updated >> text=Button").wait_for()