Attempting to update the same running component across multiple callbacks

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.

Why not just combine the callbacks? Or you could create a dcc.store that saves a +1 to it and when the state of the store reaches a certain number it allows button clicked again and resets the store to 0.

1 Like

One of the callbacks takes significantly more time than the other. I would like to populate the page with some data, while the rest is being handled by the other callback.

I’ll try out the dcc.store workflow. If I understand the workflow you proposed correctly, I’d have to add an extra output component to each callback to modify the store. The running parameter feels like the appropriate place to handle my needs.

Yeah just add a dcc.Store('store_id') at the top of the layout and an Output('store_id', 'data') and State('store_id', 'data') as needed within your callbacks and with a little tweaking you should be able to get this working as intended.