I have dashboard where one button press fires off multiple callbacks. Each of these callbacks take a different amount of time. When either of them are running, I’d like to disable the button.
If I use the same output across multiple running parameters, the first to finish will set the True state of the running triplet. The issue is that when Callback 1 finishes and sets the button to no longer be disabled, Callback 2 is still running. This makes logical sense.
Example code:
import dash
from dash import html, dcc, callback, Output, Input
import time
app = dash.Dash(__name__)
app.layout = html.Div([
html.Button('Start', id='start'),
html.Div([
html.H4('Item running 5 sec'),
dcc.Loading('', id='item1')
]),
html.Div([
html.H4('Item running 10 sec'),
dcc.Loading('', id='item2')
]),
])
@callback(
Output('item1', 'children'),
Input('start', 'n_clicks'),
running=[
(Output('start', 'disabled'), True, False)
]
)
def update_item1(clicks):
if clicks is None:
raise dash.exceptions.PreventUpdate()
time.sleep(5)
return 'Finished in 5 secs'
@callback(
Output('item2', 'children'),
Input('start', 'n_clicks'),
running=[
(Output('start', 'disabled'), True, False)
]
)
def update_item2(clicks):
if clicks is None:
raise dash.exceptions.PreventUpdate()
time.sleep(10)
return 'Finished in 10 secs'
if __name__ == '__main__':
app.run(debug=True)
My best guess at accomplishing my goal is to 1) add a new disabled component for each callback - a button, 2) update these to be disabled when running, and 3) add a pattern matching callback that returns disabled if any of the new components are. I’m running into errors in the browser consoles when using an object as an id in the Output portion of the running parameter.
running = [ (Output({'type': 'btn', 'index': 1}, 'disabled'), True, False ]
On chrome, I get a Cannot read properties of undefined (reading 'concat') at getInputHistoryState (reducer.js:57:36)
.
On firefox, I get a itempath is undefined at getInputHistoryState (reducer.js:57)
Full example
import dash
from dash import html, dcc, callback, Output, Input, ALL
import time
app = dash.Dash(__name__)
app.layout = html.Div([
html.Button('Start', id='btn'),
html.Div([
html.H4('Item running 5 sec'),
html.Button('item1 disabled', id={'type': 'btn', 'index': 1}, disabled=False),
dcc.Loading('', id='item1')
]),
html.Div([
html.H4('Item running 10 sec'),
html.Button('item2 disabled', id={'type': 'btn', 'index': 2}, disabled=False),
dcc.Loading('', id='item2')
]),
])
@callback(
Output('item1', 'children'),
Input('btn', 'n_clicks'),
running=[
(Output({'type': 'btn', 'index': 1}, 'disabled'), True, False)
]
)
def update_item1(clicks):
if clicks is None:
raise dash.exceptions.PreventUpdate()
time.sleep(5)
return 'Finished in 5 secs'
@callback(
Output('item2', 'children'),
Input('btn', 'n_clicks'),
running=[
(Output({'type': 'btn', 'index': 2}, 'disabled'), True, False)
]
)
def update_item2(clicks):
if clicks is None:
raise dash.exceptions.PreventUpdate()
time.sleep(10)
return 'Finished in 10 secs'
@callback(
Output('btn', 'disabled'),
Input({'type': 'btn', 'index': ALL}, 'disabled')
)
def update_disabled(disabled_buttons):
print(disabled_buttons)
return any(disabled_buttons)
if __name__ == '__main__':
app.run(debug=True)
Anyone have any ideas why I might be seeing these errors? Or if someone has a pointer to the code where this functionality ought to live, I would be happy to contribute.