Skip to content

[BUG] dcc.Loading target_components with * doesn't work #3340

Open
@celia-lm

Description

@celia-lm

According to the documentation:

target_components` is a dictionary where the key is the component id, and the value is the property name, or a list of property names or "*"

https://dash.plotly.com/dash-core-components/loading#target-components

However, when I've tried to implement it, I've noticed that specifying that general component property selector like {"button":"*"}, no change will trigger the dcc.Loading animation.

Other observations with the same code:

  • If I don't specify any value for target_components, every component property change of the dcc.Loading children will trigger the loading animation (this is expected).
  • If I specify a specific component property (involved in a callback) like {"button":"disabled"}, a change in the disabled property of the button will trigger the dcc.Loading anymation (this is expected).
  • I can specify any value for target_components and no validation will happen. Specifying something like {"anything":"anything"} will basically disable the dcc.Loading component.

The sample code uses Dash Ag Grid and the disabled property of the button because I discovered the issue(s) while investigating this: https://community.plotly.com/t/filter-in-ag-grid-loses-focus-while-typing/78460/10

Screen.Recording.2025-06-24.at.19.19.19.mov

In the video, the first and last options are basically the same. The option that reflects the issue described on this page is the third one: dcc.Loading([button, datatable], target_components={'button':'*'}).

Environment

dash==3.0.4
dash-ag-grid==31.3.1

Python 3.12.5

Code to replicate the issue

from dash import callback, Dash, dcc, html, Input, no_update, Output
import dash_ag_grid as dag
import pandas as pd
import time

app = Dash(__name__, suppress_callback_exceptions=True)

button = html.Button(id="button", n_clicks=0, children='Clear Filter', disabled=True)

dataframe_dict = {f'column_{i}_data': [2, 4, 6] for i in range(1,10)}
dataframe_dict['product_id'] = ['XYZ083-02ABC', 'NYZ082-02ABC', 'XYZ083-05ABC']
dataframe = pd.DataFrame(dataframe_dict)

col_defs = [{'field': 'product_id', 'type' : None}] + [
    {'field': f'column_{i}_data', 'type': 'numericColumn'} for i in range(1,10)
    ]

datatable = dag.AgGrid(
    id="datatable",
    columnDefs=col_defs,
    defaultColDef={'filter': True,'floatingFilter': True},
    rowData=dataframe.to_dict('records')
    )

radio_items = dcc.RadioItems(
    id="layout_option",
    value="layout_1",
    options=[
        {"label":"dcc.Loading([button, datatable])", "value":"layout_1"},
        {"label":"[dcc.Loading(button), datatable]", "value":"layout_2"},
        {"label":"dcc.Loading([button, datatable], target_components={'button':'*'})", "value":"layout_3"},
        {"label":"dcc.Loading([button, datatable], target_components={'button':'disabled'})", "value":"layout_4"},
        {"label":"dcc.Loading([button, datatable], target_components={'anything':'anything'})", "value":"layout_5"},
        {"label":"dcc.Loading([button, datatable], target_components=None)", "value":"layout_6"},
    ]
)

all_layouts = dict(
    layout_1 = dcc.Loading([button, datatable]),
    layout_2 = [dcc.Loading(button), datatable],
    layout_3 = dcc.Loading([button, datatable], target_components={"button":"*"}),
    layout_4 = dcc.Loading([button, datatable], target_components={"button":"disabled"}),
    layout_5 = dcc.Loading([button, datatable], target_components={"anything":"anything"}),
    layout_6 = dcc.Loading([button, datatable], target_components=None),
)

app.layout = html.Div([
    radio_items,
    html.Div(all_layouts["layout_1"], id="app_layout")
])

@callback(Output("datatable", 'filterModel'),
          Input("button", 'n_clicks'),
          prevent_initial_call=True)
def button_filter(n_clicks):
    return {} if n_clicks else no_update

@callback(Output("button", 'disabled'),
          Input("datatable", 'filterModel'),
          prevent_initial_call=True)
def enable_button_filter(filter_query):
    # simulate a long running process
    time.sleep(3)
    return filter_query == {}

# callback that I've created to test the different options
@callback(
    Output("app_layout", "children"),
    Input("layout_option", "value"),
    prevent_initial_call=True
)
def update_layout(selected_layout):
    return all_layouts[selected_layout]

if __name__ == '__main__':
    app.run(debug=True)

Metadata

Metadata

Assignees

Labels

P1needed for current cyclebugsomething brokencscustomer success

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions