Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
All notable changes to `dash` will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org/).

## [UNRELEASED]

## Fixed
- [#3353](https://github.com/plotly/dash/pull/3353) Support pattern-matching/dict ids in `dcc.Loading` `target_components`


# [3.1.1] - 2025-06-29

## Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ function Loading({
custom_spinner,
}) {
const ctx = window.dash_component_api.useDashContext();

const loading = ctx.useSelector(
loadingSelector(ctx.componentPath, target_components),
equals
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from multiprocessing import Lock
from dash import Dash, Input, Output, dcc, html
from dash.dependencies import stringify_id
from dash.testing import wait
import time

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


# multiple components, only one triggers the spinner
def test_ldcp010_loading_component_target_components(dash_dcc):

# update multiple props of same component, only targeted id/prop triggers spinner
# test that target_components id can be a dict id
def test_ldcp011_loading_component_target_components(dash_dcc):
lock = Lock()

app = Dash(__name__)
Expand All @@ -425,53 +426,61 @@ def test_ldcp010_loading_component_target_components(dash_dcc):
[
dcc.Loading(
[
html.Button(id="btn-1"),
html.Button(id={"type": "button", "index": "one"}),
html.Button(id="btn-2"),
html.Button(id="btn-3"),
],
className="loading-1",
target_components={"btn-2": "children"},
target_components={
stringify_id({"type": "button", "index": "one"}): "className"
},
)
],
id="root",
)

@app.callback(Output("btn-1", "children"), [Input("btn-2", "n_clicks")])
@app.callback(
Output({"type": "button", "index": "one"}, "children"),
[Input("btn-2", "n_clicks")],
)
def updateDiv1(n_clicks):
if n_clicks:
with lock:
return "changed 1"

return "content 1"

@app.callback(Output("btn-2", "children"), [Input("btn-1", "n_clicks")])
@app.callback(
Output({"type": "button", "index": "one"}, "className"),
[Input("btn-3", "n_clicks")],
)
def updateDiv2(n_clicks):
if n_clicks:
with lock:
return "changed 2"

return "content 2"
return "new-class"
return ""

dash_dcc.start_server(app)

dash_dcc.wait_for_text_to_equal("#btn-1", "content 1")
dash_dcc.wait_for_text_to_equal("#btn-2", "content 2")

with lock:
dash_dcc.find_element("#btn-1").click()
btn1id = "#" + stringify_id({"type": "button", "index": "one"})

dash_dcc.find_element(".loading-1 .dash-spinner")
dash_dcc.wait_for_text_to_equal("#btn-2", "")

dash_dcc.wait_for_text_to_equal("#btn-2", "changed 2")
dash_dcc.wait_for_text_to_equal(btn1id, "content 1")

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

dash_dcc.wait_for_text_to_equal("#btn-1", "changed 1")
spinners = dash_dcc.find_elements(".loading-1 .dash-spinner")
dash_dcc.wait_for_text_to_equal(btn1id, "")
dash_dcc.wait_for_text_to_equal(btn1id, "changed 1")
assert spinners == []

with lock:
dash_dcc.find_element("#btn-3").click()

dash_dcc.find_element(".loading-1 .dash-spinner")
dash_dcc.wait_for_text_to_equal(btn1id, "")

dash_dcc.wait_for_class_to_equal(btn1id, "new-class")

assert dash_dcc.get_logs() == []


Expand Down
2 changes: 2 additions & 0 deletions dash/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
CeleryManager,
DiskcacheManager,
)
from ._utils import stringify_id # noqa: F401,E402


from ._pages import register_page, PAGE_REGISTRY as page_registry # noqa: F401,E402
Expand Down Expand Up @@ -90,4 +91,5 @@ def _jupyter_nbextension_paths():
"jupyter_dash",
"ctx",
"hooks",
"stringify_id",
]
2 changes: 1 addition & 1 deletion dash/dash-renderer/src/actions/callbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,7 @@ export function executeCallback(
const loadingOutputs = outputs.map(out => ({
path: getPath(paths, out.id),
property: out.property?.split('@')[0],
id: out.id
id: (window as any).dash_component_api.stringifyId(out.id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the renderer we can use the one from:
import {stringifyId} from './actions/dependencies';

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops - of course!
Fixed

}));
dispatch(loading(loadingOutputs));
try {
Expand Down
Loading