From 677abb39dc13796c3e5c3cb2b59473ad33611618 Mon Sep 17 00:00:00 2001 From: BSd3v <82055130+BSd3v@users.noreply.github.com> Date: Tue, 8 Apr 2025 10:32:59 -0400 Subject: [PATCH 1/6] allows for the componentPath to be updated when moving around in the children and triggering `setProps` --- dash/dash-renderer/src/wrapper/DashWrapper.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dash/dash-renderer/src/wrapper/DashWrapper.tsx b/dash/dash-renderer/src/wrapper/DashWrapper.tsx index 82bee5b0ba..98c2683dcb 100644 --- a/dash/dash-renderer/src/wrapper/DashWrapper.tsx +++ b/dash/dash-renderer/src/wrapper/DashWrapper.tsx @@ -65,6 +65,7 @@ function DashWrapper({ const dispatch = useDispatch(); const memoizedKeys: MutableRefObject = useRef({}); const newRender = useRef(false); + const renderedPath: any = useRef(null); let renderComponent: any = null; let renderComponentProps: any = null; let renderH: any = null; @@ -90,9 +91,10 @@ function DashWrapper({ } else { newRender.current = false; } + renderedPath.current = componentPath }, [_newRender]); - const setProps = (newProps: UpdatePropsPayload) => { + const setProps = useCallback((newProps: UpdatePropsPayload) => { const {id} = renderComponentProps; const {_dash_error, ...restProps} = newProps; @@ -101,7 +103,7 @@ function DashWrapper({ dispatch((dispatch, getState) => { const currentState = getState(); const {graphs} = currentState; - const oldLayout = getComponentLayout(componentPath, currentState); + const oldLayout = getComponentLayout(renderedPath.current, currentState); if (!oldLayout) return; const {props: oldProps} = oldLayout; if (!oldProps) return; @@ -144,13 +146,13 @@ function DashWrapper({ dispatch( updateProps({ props: changedProps, - itempath: componentPath, + itempath: renderedPath.current, renderType: 'internal' }) ); }); }); - }; + }, [componentPath]); const createContainer = useCallback( (container, containerPath, _childNewRender, key = undefined) => { From d2afce4e46824af08f1310497ccfc48563688e2d Mon Sep 17 00:00:00 2001 From: BSd3v <82055130+BSd3v@users.noreply.github.com> Date: Tue, 8 Apr 2025 11:12:46 -0400 Subject: [PATCH 2/6] removing `useCallback` from `setProps` and running format --- dash/dash-renderer/src/wrapper/DashWrapper.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/dash/dash-renderer/src/wrapper/DashWrapper.tsx b/dash/dash-renderer/src/wrapper/DashWrapper.tsx index 98c2683dcb..635d6771d2 100644 --- a/dash/dash-renderer/src/wrapper/DashWrapper.tsx +++ b/dash/dash-renderer/src/wrapper/DashWrapper.tsx @@ -91,10 +91,10 @@ function DashWrapper({ } else { newRender.current = false; } - renderedPath.current = componentPath + renderedPath.current = componentPath; }, [_newRender]); - const setProps = useCallback((newProps: UpdatePropsPayload) => { + const setProps = (newProps: UpdatePropsPayload) => { const {id} = renderComponentProps; const {_dash_error, ...restProps} = newProps; @@ -103,7 +103,10 @@ function DashWrapper({ dispatch((dispatch, getState) => { const currentState = getState(); const {graphs} = currentState; - const oldLayout = getComponentLayout(renderedPath.current, currentState); + const oldLayout = getComponentLayout( + renderedPath.current, + currentState + ); if (!oldLayout) return; const {props: oldProps} = oldLayout; if (!oldProps) return; @@ -152,7 +155,7 @@ function DashWrapper({ ); }); }); - }, [componentPath]); + }; const createContainer = useCallback( (container, containerPath, _childNewRender, key = undefined) => { From 37026836f93c41d517223132a74840c334ec2f21 Mon Sep 17 00:00:00 2001 From: BSd3v <82055130+BSd3v@users.noreply.github.com> Date: Tue, 8 Apr 2025 11:38:41 -0400 Subject: [PATCH 3/6] swapping initial value --- dash/dash-renderer/src/wrapper/DashWrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/dash-renderer/src/wrapper/DashWrapper.tsx b/dash/dash-renderer/src/wrapper/DashWrapper.tsx index 635d6771d2..370c385ba5 100644 --- a/dash/dash-renderer/src/wrapper/DashWrapper.tsx +++ b/dash/dash-renderer/src/wrapper/DashWrapper.tsx @@ -65,7 +65,7 @@ function DashWrapper({ const dispatch = useDispatch(); const memoizedKeys: MutableRefObject = useRef({}); const newRender = useRef(false); - const renderedPath: any = useRef(null); + const renderedPath = useRef(componentPath); let renderComponent: any = null; let renderComponentProps: any = null; let renderH: any = null; From 94123b34bea3154f36f952156c28190ee92fab88 Mon Sep 17 00:00:00 2001 From: BSd3v <82055130+BSd3v@users.noreply.github.com> Date: Tue, 8 Apr 2025 12:40:11 -0400 Subject: [PATCH 4/6] adding test for reordering children and making sure the `setProps` still goes to the right place --- .../renderer/test_children_reorder.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 tests/integration/renderer/test_children_reorder.py diff --git a/tests/integration/renderer/test_children_reorder.py b/tests/integration/renderer/test_children_reorder.py new file mode 100644 index 0000000000..8c0205ad57 --- /dev/null +++ b/tests/integration/renderer/test_children_reorder.py @@ -0,0 +1,84 @@ +import time +from dash import Dash, Input, Output, html, dcc, State, ALL + + +class Section: + def __init__(self, idx): + self.idx = idx + self.options = ["A", "B", "C"] + + @property + def section_id(self): + return {"type": "section-container", "id": self.idx} + + @property + def dropdown_id(self): + return {"type": "dropdown", "id": self.idx} + + @property + def swap_btn_id(self): + return {"type": "swap-btn", "id": self.idx} + + def get_layout(self) -> html.Div: + layout = html.Div( + id=self.section_id, + children=[ + html.H1(f"I am section {self.idx}"), + html.Button( + "SWAP", + id=self.swap_btn_id, + n_clicks=0, + className=f"swap_button_{self.idx}", + ), + dcc.Dropdown( + self.options, + id=self.dropdown_id, + multi=True, + value=[], + className=f"dropdown_{self.idx}", + ), + ], + ) + return layout + + +def test_roc001_reorder_children(dash_duo): + app = Dash() + + app.layout = html.Div( + id="main-app", children=[*[Section(idx=i).get_layout() for i in range(2)]] + ) + + @app.callback( + Output("main-app", "children"), + Input({"type": "swap-btn", "id": ALL}, "n_clicks"), + State("main-app", "children"), + prevent_initial_call=True, + ) + def swap_button_action(n_clicks, children): + if any(n > 0 for n in n_clicks): + return children[::-1] + + dash_duo.start_server(app) + + for i in range(2): + dash_duo.wait_for_text_to_equal("h1", f"I am section {i}") + dash_duo.wait_for_text_to_equal( + f".dropdown_{i} .Select-multi-value-wrapper", "Select..." + ) + dash_duo.find_element(f".dropdown_{i}").click() + dash_duo.find_element(f".dropdown_{i} .VirtualizedSelectOption").click() + dash_duo.wait_for_text_to_equal( + f".dropdown_{i} .Select-multi-value-wrapper", "×A\n " + ) + dash_duo.find_element(f".dropdown_{i}").click() + dash_duo.find_element(f".dropdown_{i} .VirtualizedSelectOption").click() + dash_duo.wait_for_text_to_equal( + f".dropdown_{i} .Select-multi-value-wrapper", "×A\n ×B\n " + ) + dash_duo.find_element(f".dropdown_{i}").click() + dash_duo.find_element(f".dropdown_{i} .VirtualizedSelectOption").click() + dash_duo.wait_for_text_to_equal( + f".dropdown_{i} .Select-multi-value-wrapper", "×A\n ×B\n ×C\n " + ) + dash_duo.find_element(f".swap_button_{i}").click() From 7f6018b7db5cc86026eb8bc477d72cd835ff4f78 Mon Sep 17 00:00:00 2001 From: BSd3v <82055130+BSd3v@users.noreply.github.com> Date: Tue, 8 Apr 2025 13:21:01 -0400 Subject: [PATCH 5/6] fixing for lint --- tests/integration/renderer/test_children_reorder.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/integration/renderer/test_children_reorder.py b/tests/integration/renderer/test_children_reorder.py index 8c0205ad57..3e92c5befe 100644 --- a/tests/integration/renderer/test_children_reorder.py +++ b/tests/integration/renderer/test_children_reorder.py @@ -1,4 +1,3 @@ -import time from dash import Dash, Input, Output, html, dcc, State, ALL @@ -82,3 +81,9 @@ def swap_button_action(n_clicks, children): f".dropdown_{i} .Select-multi-value-wrapper", "×A\n ×B\n ×C\n " ) dash_duo.find_element(f".swap_button_{i}").click() + dash_duo.wait_for_text_to_equal( + f".dropdown_{0} .Select-multi-value-wrapper", "×A\n ×B\n ×C\n " + ) + dash_duo.wait_for_text_to_equal( + f".dropdown_{1} .Select-multi-value-wrapper", "×A\n ×B\n ×C\n " + ) From 3c85458f97049e41028219b6cb688a8c7e049e54 Mon Sep 17 00:00:00 2001 From: BSd3v <82055130+BSd3v@users.noreply.github.com> Date: Tue, 8 Apr 2025 13:48:23 -0400 Subject: [PATCH 6/6] adding changelog entry --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0fcd49690..379c34f3af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). +## [unreleased] + +## Fixed +- [#3264](https://github.com/plotly/dash/pull/3264) Fixed an issue where moving components inside of children would not update the `setProps` path, leading to hashes being incorrect + ## [3.0.2] - 2025-04-01 ## Changed