Disabling Button During Callback Function

Hello,

I have a dash app that is displaying a data table with a button. When the button is pressed a callback function is triggered which takes the data tables data component and sends it to a python script. The python script requires minutes to run but eventually returns a new dataframe, which I use to update the dash data table with.

I would like to make it impossible for the end user to click the button multiple times while they are waiting for the script to finish. I would also like to give the end user some sort of signal that the python script is running and they should be patient.

Is there a standard approach for this?

Hi @wannabedatascientist and welcome to the Dash community :slight_smile:

Check out this section of the docs:

2 Likes

Thank you. I’ve been reading through that page and know nothing about the two backend options. I am trying to host this app on an internal network.

I was thinking I could have two callback functions. The first one has the n_clicks set as an input. When the button is clicked it will set a dcc.store components data property to True, to signify that computation is happening.

The changing of the dcc.store components data property will then trigger the second callback function. The second callback function will use the data property of the data table as a parameter in a python script. It will wait a few minutes for the script to complete, then return updated information to the dash data table. It will also set the dcc.store data property to false, to signify no computation is happening.

The issue I see with this is that the second callback function will use the dcc.store data property as both an input and an output. But if that’s ok then:

button is pressed, triggering first callback function, and setting dcc.store component data to true
second callback function triggered from changing of dcc.store data value. It takes a few minutes, then returns some information, as well as updating dcc.store data value, setting it back to false

@wannabedatascientist all that is possible with background callbacks.

More inspiration, this one without background callback for indicating that something is happening:

An example for a background callback:

2 Likes

This all made me wonder whether your original question can be answered using basic Dash functionality, without background callbacks or advanced functionality like to_plotly_json() in a clientside callback.

The example code below is all basic Dash functionality, and I think shows the kind of thing you want. The first and second buttons are disabled (and a ‘loading’ is shown) during the pause triggered by the first button. The third button remains enabled, and appears to happily respond to clicks during the pause. (I’m unsure how reliably this kind of thing can be done though).

(To answer your other question, a callback can use the same dcc.Store as both an Input and an Output, though it’s not needed in this example)

from dash import html, Dash, callback, Input, Output, dcc
from time import sleep

app = Dash(__name__)
app.layout = html.Div(
    [
        dcc.Store(id='store-trigger'),
        html.Button("Pause for a while", id="button-pause"),
        dcc.Loading(
            id="loading-1",
            type="default",
            children=html.Div(id="pause-nclicks", children=['0']),
        ),
        html.Button("Disabled during pause", id="button-dependent"),
        html.Div(id="dependent-nclicks", children=['0']),
        html.Button("Enabled during pause", id="button-independent"),
        html.Div(id="independent-nclicks", children=['0']),
    ]
)

# -- Pause button clicked: Disable 'pause' and 'dependent' buttons and update store
@callback(
    Output("store-trigger", "data"),
    Output("button-pause","disabled", allow_duplicate=True),
    Output("button-dependent","disabled", allow_duplicate=True),
    Input("button-pause", "n_clicks"),
    prevent_initial_call=True
)
def  start_pause(nclicks):
    return nclicks, True, True

# -- Store updated: do the long calculation. Enable 'pause' and 'dependent' button when it is finished
@callback(
    Output("pause-nclicks", "children"),
    Output("button-pause","disabled", allow_duplicate=True),
    Output("button-dependent","disabled", allow_duplicate=True),
    Input("store-trigger", "data"),
    prevent_initial_call=True
)
def  do_pause(nclicks):
    sleep(5)
    return str(nclicks), False, False

# -- 'Dependent' button clicked
@callback(
    Output("dependent-nclicks", "children"),
    Input("button-dependent", "n_clicks"),
    prevent_initial_call=True
)
def  update_dependent(nclicks):
    return str(nclicks)

# -- 'Independent' button clicked
@callback(
    Output("independent-nclicks", "children"),
    Input("button-independent", "n_clicks"),
    prevent_initial_call=True
)
def  update_independent(nclicks):
    return str(nclicks)

if __name__ == '__main__':
    app.run_server(debug=False,  host='0.0.0.0')

3 Likes

There is also Updating props when a callback is running:

3 Likes