Skip to content

Commit a14b0c1

Browse files
authored
Merge pull request #3353 from AnnMarieW/fix-dcc-loading
Allow dict id in target_components
2 parents 6b21ec9 + 91626f1 commit a14b0c1

File tree

5 files changed

+42
-24
lines changed

5 files changed

+42
-24
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
All notable changes to `dash` will be documented in this file.
33
This project adheres to [Semantic Versioning](https://semver.org/).
44

5+
## [UNRELEASED]
6+
7+
## Fixed
8+
- [#3353](https://github.com/plotly/dash/pull/3353) Support pattern-matching/dict ids in `dcc.Loading` `target_components`
9+
10+
511
# [3.1.1] - 2025-06-29
612

713
## Fixed

components/dash-core-components/src/components/Loading.react.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ function Loading({
8787
custom_spinner,
8888
}) {
8989
const ctx = window.dash_component_api.useDashContext();
90+
9091
const loading = ctx.useSelector(
9192
loadingSelector(ctx.componentPath, target_components),
9293
equals

components/dash-core-components/tests/integration/loading/test_loading_component.py

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from multiprocessing import Lock
22
from dash import Dash, Input, Output, dcc, html
3+
from dash.dependencies import stringify_id
34
from dash.testing import wait
45
import time
56

@@ -414,9 +415,9 @@ def updateDiv(n_clicks):
414415
assert dash_dcc.get_logs() == []
415416

416417

417-
# multiple components, only one triggers the spinner
418-
def test_ldcp010_loading_component_target_components(dash_dcc):
419-
418+
# update multiple props of same component, only targeted id/prop triggers spinner
419+
# test that target_components id can be a dict id
420+
def test_ldcp011_loading_component_target_components(dash_dcc):
420421
lock = Lock()
421422

422423
app = Dash(__name__)
@@ -425,53 +426,61 @@ def test_ldcp010_loading_component_target_components(dash_dcc):
425426
[
426427
dcc.Loading(
427428
[
428-
html.Button(id="btn-1"),
429+
html.Button(id={"type": "button", "index": "one"}),
429430
html.Button(id="btn-2"),
431+
html.Button(id="btn-3"),
430432
],
431433
className="loading-1",
432-
target_components={"btn-2": "children"},
434+
target_components={
435+
stringify_id({"type": "button", "index": "one"}): "className"
436+
},
433437
)
434438
],
435439
id="root",
436440
)
437441

438-
@app.callback(Output("btn-1", "children"), [Input("btn-2", "n_clicks")])
442+
@app.callback(
443+
Output({"type": "button", "index": "one"}, "children"),
444+
[Input("btn-2", "n_clicks")],
445+
)
439446
def updateDiv1(n_clicks):
440447
if n_clicks:
441448
with lock:
442449
return "changed 1"
443-
444450
return "content 1"
445451

446-
@app.callback(Output("btn-2", "children"), [Input("btn-1", "n_clicks")])
452+
@app.callback(
453+
Output({"type": "button", "index": "one"}, "className"),
454+
[Input("btn-3", "n_clicks")],
455+
)
447456
def updateDiv2(n_clicks):
448457
if n_clicks:
449458
with lock:
450-
return "changed 2"
451-
452-
return "content 2"
459+
return "new-class"
460+
return ""
453461

454462
dash_dcc.start_server(app)
455463

456-
dash_dcc.wait_for_text_to_equal("#btn-1", "content 1")
457-
dash_dcc.wait_for_text_to_equal("#btn-2", "content 2")
458-
459-
with lock:
460-
dash_dcc.find_element("#btn-1").click()
464+
btn1id = "#" + stringify_id({"type": "button", "index": "one"})
461465

462-
dash_dcc.find_element(".loading-1 .dash-spinner")
463-
dash_dcc.wait_for_text_to_equal("#btn-2", "")
464-
465-
dash_dcc.wait_for_text_to_equal("#btn-2", "changed 2")
466+
dash_dcc.wait_for_text_to_equal(btn1id, "content 1")
466467

467468
with lock:
468469
dash_dcc.find_element("#btn-2").click()
469-
spinners = dash_dcc.find_elements(".loading-1 .dash-spinner")
470-
dash_dcc.wait_for_text_to_equal("#btn-1", "")
471470

472-
dash_dcc.wait_for_text_to_equal("#btn-1", "changed 1")
471+
spinners = dash_dcc.find_elements(".loading-1 .dash-spinner")
472+
dash_dcc.wait_for_text_to_equal(btn1id, "")
473+
dash_dcc.wait_for_text_to_equal(btn1id, "changed 1")
473474
assert spinners == []
474475

476+
with lock:
477+
dash_dcc.find_element("#btn-3").click()
478+
479+
dash_dcc.find_element(".loading-1 .dash-spinner")
480+
dash_dcc.wait_for_text_to_equal(btn1id, "")
481+
482+
dash_dcc.wait_for_class_to_equal(btn1id, "new-class")
483+
475484
assert dash_dcc.get_logs() == []
476485

477486

dash/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
CeleryManager,
3131
DiskcacheManager,
3232
)
33+
from ._utils import stringify_id # noqa: F401,E402
3334

3435

3536
from ._pages import register_page, PAGE_REGISTRY as page_registry # noqa: F401,E402
@@ -90,4 +91,5 @@ def _jupyter_nbextension_paths():
9091
"jupyter_dash",
9192
"ctx",
9293
"hooks",
94+
"stringify_id",
9395
]

dash/dash-renderer/src/actions/callbacks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,7 @@ export function executeCallback(
753753
const loadingOutputs = outputs.map(out => ({
754754
path: getPath(paths, out.id),
755755
property: out.property?.split('@')[0],
756-
id: out.id
756+
id: stringifyId(out.id)
757757
}));
758758
dispatch(loading(loadingOutputs));
759759
try {

0 commit comments

Comments
 (0)