From d91fe7c0f1225737cef46c64709a081ee0e27cdc Mon Sep 17 00:00:00 2001
From: BSd3v <82055130+BSd3v@users.noreply.github.com>
Date: Mon, 2 Jun 2025 12:55:19 -0400
Subject: [PATCH 1/6] adds flag to `ExternalWrapper` to identify if the
component is a temporary insertion
---
dash/dash-renderer/src/wrapper/ExternalWrapper.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/dash/dash-renderer/src/wrapper/ExternalWrapper.tsx b/dash/dash-renderer/src/wrapper/ExternalWrapper.tsx
index a6b1dad13f..5bb26fef73 100644
--- a/dash/dash-renderer/src/wrapper/ExternalWrapper.tsx
+++ b/dash/dash-renderer/src/wrapper/ExternalWrapper.tsx
@@ -18,7 +18,7 @@ type Props = {
/**
* For rendering components that are out of the regular layout tree.
*/
-function ExternalWrapper({component, componentPath}: Props) {
+function ExternalWrapper({component, componentPath}: Props, temp?: boolean = false) {
const dispatch: any = useDispatch();
const [inserted, setInserted] = useState(false);
@@ -33,7 +33,7 @@ function ExternalWrapper({component, componentPath}: Props) {
);
setInserted(true);
return () => {
- dispatch(removeComponent({componentPath}));
+ if (temp) dispatch(removeComponent({componentPath}));
};
}, []);
From 249dfc4e4ae602bf3eb6c3fca347746eb9e92b71 Mon Sep 17 00:00:00 2001
From: BSd3v <82055130+BSd3v@users.noreply.github.com>
Date: Mon, 2 Jun 2025 13:12:51 -0400
Subject: [PATCH 2/6] fixing function and lint
---
dash/dash-renderer/src/wrapper/ExternalWrapper.tsx | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/dash/dash-renderer/src/wrapper/ExternalWrapper.tsx b/dash/dash-renderer/src/wrapper/ExternalWrapper.tsx
index 5bb26fef73..807a8e7040 100644
--- a/dash/dash-renderer/src/wrapper/ExternalWrapper.tsx
+++ b/dash/dash-renderer/src/wrapper/ExternalWrapper.tsx
@@ -18,7 +18,11 @@ type Props = {
/**
* For rendering components that are out of the regular layout tree.
*/
-function ExternalWrapper({component, componentPath}: Props, temp?: boolean = false) {
+function ExternalWrapper({
+ component,
+ componentPath,
+ temp = false
+}: Props & {temp?: boolean}) {
const dispatch: any = useDispatch();
const [inserted, setInserted] = useState(false);
@@ -33,7 +37,9 @@ function ExternalWrapper({component, componentPath}: Props, temp?: boolean = fal
);
setInserted(true);
return () => {
- if (temp) dispatch(removeComponent({componentPath}));
+ if (temp) {
+ dispatch(removeComponent({componentPath}));
+ }
};
}, []);
From 9dd243d2e35bca822fc380ef14c950d6a34bb725 Mon Sep 17 00:00:00 2001
From: BSd3v <82055130+BSd3v@users.noreply.github.com>
Date: Mon, 2 Jun 2025 15:16:05 -0400
Subject: [PATCH 3/6] updating definition for temp
---
dash/dash-renderer/src/wrapper/ExternalWrapper.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/dash/dash-renderer/src/wrapper/ExternalWrapper.tsx b/dash/dash-renderer/src/wrapper/ExternalWrapper.tsx
index 807a8e7040..bdf7848318 100644
--- a/dash/dash-renderer/src/wrapper/ExternalWrapper.tsx
+++ b/dash/dash-renderer/src/wrapper/ExternalWrapper.tsx
@@ -13,6 +13,7 @@ import {
type Props = {
component: DashComponent;
componentPath: DashLayoutPath;
+ temp?: boolean; // If true, the component will be removed on unmount.
};
/**
@@ -22,7 +23,7 @@ function ExternalWrapper({
component,
componentPath,
temp = false
-}: Props & {temp?: boolean}) {
+}: Props) {
const dispatch: any = useDispatch();
const [inserted, setInserted] = useState(false);
From 0988888bf4b415b038ecc16b50fe63685107f29a Mon Sep 17 00:00:00 2001
From: BSd3v <82055130+BSd3v@users.noreply.github.com>
Date: Mon, 2 Jun 2025 16:32:24 -0400
Subject: [PATCH 4/6] adding test for temp component with `ExternalWrapper`
---
.../src/components/ExternalComponent.js | 10 ++--
.../renderer/test_external_component.py | 48 +++++++++++++++++++
2 files changed, 54 insertions(+), 4 deletions(-)
diff --git a/@plotly/dash-test-components/src/components/ExternalComponent.js b/@plotly/dash-test-components/src/components/ExternalComponent.js
index 64d2efc515..8c592e33de 100644
--- a/@plotly/dash-test-components/src/components/ExternalComponent.js
+++ b/@plotly/dash-test-components/src/components/ExternalComponent.js
@@ -2,10 +2,9 @@ import React from 'react';
import PropTypes from 'prop-types';
-const ExternalComponent = ({ id, text, input_id, extra_component }) => {
+const ExternalComponent = ({ id, text, input_id, extra_component, extra_component_temp }) => {
const ctx = window.dash_component_api.useDashContext();
const ExternalWrapper = window.dash_component_api.ExternalWrapper;
-
return (
{text && {
id: input_id
}
}}
- componentPath={[...ctx.componentPath, 'external']}
+ componentPath={[JSON.stringify(ctx.componentPath), 'text']}
+ temp={true}
/>}
{
extra_component &&
}
)
@@ -39,6 +40,7 @@ ExternalComponent.propTypes = {
namespace: PropTypes.string,
props: PropTypes.object,
}),
+ extra_component_temp: PropTypes.bool,
};
export default ExternalComponent;
diff --git a/tests/integration/renderer/test_external_component.py b/tests/integration/renderer/test_external_component.py
index c0ab76fcc3..7ce7e8fdc2 100644
--- a/tests/integration/renderer/test_external_component.py
+++ b/tests/integration/renderer/test_external_component.py
@@ -59,3 +59,51 @@ def click(*_):
dash_duo.wait_for_text_to_equal("#out", "clicked")
assert dash_duo.get_logs() == []
+
+def test_rext002_render_external_component_temp(dash_duo):
+ app = Dash()
+ app.layout = html.Div(
+ [
+ dcc.Tabs(
+ [
+ dcc.Tab(label="Tab 1", children=[
+ ExternalComponent(
+ id="ext",
+ extra_component={
+ "type": "Div",
+ "namespace": "dash_html_components",
+ "props": {
+ "id": "extra",
+ "children": [
+ html.Div("extra children", id={"type": "extra", "index": 1})
+ ],
+ },
+ },
+ extra_component_temp=True
+ ),
+ ]),
+ dcc.Tab(label='Tab 2', children=[
+ ExternalComponent(
+ id="without-id",
+ text="without-id",
+ ),
+ ])
+ ]
+ ),
+ ]
+ )
+
+ dash_duo.start_server(app)
+ dash_duo.wait_for_text_to_equal("#extra", "extra children")
+
+ dash_duo.find_element('.tab:nth-child(2)').click()
+ assert dash_duo.find_element("#without-id > input").get_attribute('value') == "without-id"
+
+ dash_duo.find_element('.tab').click()
+ dash_duo.find_element("#ext")
+ assert len(dash_duo.find_elements('#ext > *')) == 0, "extra component should be removed"
+
+ dash_duo.find_element('.tab:nth-child(2)').click()
+ assert dash_duo.find_element("#without-id > input").get_attribute('value') == "without-id"
+
+ assert dash_duo.get_logs() == []
From 7de127a3677a5b53f3377fa5590e31087bf2dd3b Mon Sep 17 00:00:00 2001
From: BSd3v <82055130+BSd3v@users.noreply.github.com>
Date: Mon, 2 Jun 2025 16:34:17 -0400
Subject: [PATCH 5/6] fixing for lint
---
.../src/wrapper/ExternalWrapper.tsx | 6 +-
.../renderer/test_external_component.py | 72 ++++++++++++-------
2 files changed, 46 insertions(+), 32 deletions(-)
diff --git a/dash/dash-renderer/src/wrapper/ExternalWrapper.tsx b/dash/dash-renderer/src/wrapper/ExternalWrapper.tsx
index bdf7848318..78807b2145 100644
--- a/dash/dash-renderer/src/wrapper/ExternalWrapper.tsx
+++ b/dash/dash-renderer/src/wrapper/ExternalWrapper.tsx
@@ -19,11 +19,7 @@ type Props = {
/**
* For rendering components that are out of the regular layout tree.
*/
-function ExternalWrapper({
- component,
- componentPath,
- temp = false
-}: Props) {
+function ExternalWrapper({component, componentPath, temp = false}: Props) {
const dispatch: any = useDispatch();
const [inserted, setInserted] = useState(false);
diff --git a/tests/integration/renderer/test_external_component.py b/tests/integration/renderer/test_external_component.py
index 7ce7e8fdc2..ec2fcfef12 100644
--- a/tests/integration/renderer/test_external_component.py
+++ b/tests/integration/renderer/test_external_component.py
@@ -60,34 +60,44 @@ def click(*_):
assert dash_duo.get_logs() == []
+
def test_rext002_render_external_component_temp(dash_duo):
app = Dash()
app.layout = html.Div(
[
dcc.Tabs(
[
- dcc.Tab(label="Tab 1", children=[
- ExternalComponent(
- id="ext",
- extra_component={
- "type": "Div",
- "namespace": "dash_html_components",
- "props": {
- "id": "extra",
- "children": [
- html.Div("extra children", id={"type": "extra", "index": 1})
- ],
+ dcc.Tab(
+ label="Tab 1",
+ children=[
+ ExternalComponent(
+ id="ext",
+ extra_component={
+ "type": "Div",
+ "namespace": "dash_html_components",
+ "props": {
+ "id": "extra",
+ "children": [
+ html.Div(
+ "extra children",
+ id={"type": "extra", "index": 1},
+ )
+ ],
+ },
},
- },
- extra_component_temp=True
- ),
- ]),
- dcc.Tab(label='Tab 2', children=[
- ExternalComponent(
- id="without-id",
- text="without-id",
- ),
- ])
+ extra_component_temp=True,
+ ),
+ ],
+ ),
+ dcc.Tab(
+ label="Tab 2",
+ children=[
+ ExternalComponent(
+ id="without-id",
+ text="without-id",
+ ),
+ ],
+ ),
]
),
]
@@ -96,14 +106,22 @@ def test_rext002_render_external_component_temp(dash_duo):
dash_duo.start_server(app)
dash_duo.wait_for_text_to_equal("#extra", "extra children")
- dash_duo.find_element('.tab:nth-child(2)').click()
- assert dash_duo.find_element("#without-id > input").get_attribute('value') == "without-id"
+ dash_duo.find_element(".tab:nth-child(2)").click()
+ assert (
+ dash_duo.find_element("#without-id > input").get_attribute("value")
+ == "without-id"
+ )
- dash_duo.find_element('.tab').click()
+ dash_duo.find_element(".tab").click()
dash_duo.find_element("#ext")
- assert len(dash_duo.find_elements('#ext > *')) == 0, "extra component should be removed"
+ assert (
+ len(dash_duo.find_elements("#ext > *")) == 0
+ ), "extra component should be removed"
- dash_duo.find_element('.tab:nth-child(2)').click()
- assert dash_duo.find_element("#without-id > input").get_attribute('value') == "without-id"
+ dash_duo.find_element(".tab:nth-child(2)").click()
+ assert (
+ dash_duo.find_element("#without-id > input").get_attribute("value")
+ == "without-id"
+ )
assert dash_duo.get_logs() == []
From 13042c83bb4777f608a00466f5cf82de76f8e454 Mon Sep 17 00:00:00 2001
From: BSd3v <82055130+BSd3v@users.noreply.github.com>
Date: Mon, 2 Jun 2025 16:36:49 -0400
Subject: [PATCH 6/6] updating changelog
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 499c393eca..90c2d0bd2f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- [#3279](https://github.com/plotly/dash/pull/3279) Fix an issue where persisted values were incorrectly pruned when updated via callback. Now, callback returned values are correctly stored in the persistence storage. Fix [#2678](https://github.com/plotly/dash/issues/2678)
- [#3298](https://github.com/plotly/dash/pull/3298) Fix dev_only resources filtering.
- [#3315](https://github.com/plotly/dash/pull/3315) Fix pages module is package check.
+- [#3319](https://github.com/plotly/dash/pull/3319) Fix issue where `ExternalWrapper` would remove props from the parent component, now there is a `temp` that is passed to check if it should be removed on unmount.
## Added
- [#3294](https://github.com/plotly/dash/pull/3294) Added the ability to pass `allow_optional` to Input and State to allow callbacks to work even if these components are not in the dash layout.