Description
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)