In a Dash 2.11 application I need to disable a button for 5 seconds. Here is the definition of my button:
dbc.Button(
"Expand",
id=expansion_button_id,
className="primary-btn mt-2 btn-small",
n_clicks=0,
disabled=False,
),
Here is the definition of the client side callback:
self.app.clientside_callback(
ClientsideFunction(namespace="foo", function_name="toggle_expand_button"),
Output(self._expand_id, "disabled"),
[
Input(self._expand_id, "id"),
Input(self._expand_id, "n_clicks")
],
)
and this is my JavaScript function:
toggle_expand_button: function(component_id, n_clicks) {
console.log("component_id: " + component_id)
console.log("n_clicks: " + n_clicks)
if (!n_clicks) {
return false
}
if (n_clicks > 0) {
setTimeout(function() {
document.getElementById(component_id).disabled = false;
}, 5000); // 5 seconds
return true;
}
return true
}
The button works as expected until the 5 seconds are over, but after this point no click action is fired even though the button does no longer appear disabled. What's wrong with my code?
The problem is that from Dash's perspective your dbc.Button
component's disabled
property value is still True
.
In Dash apps, the dash-renderer project...contains all of the "state" of the application and it passes those properties into the individual components. When a component's properties change through user interaction (e.g. typing into an or hovering on a graph), the component needs to call setProps with the new values of the property. Dash's frontend (dash-renderer) will then rerender the component with the new property and make the necessary API calls to Dash's Python server callbacks.
https://dash.plotly.com/react-for-python-developers
Dash is not notified when you make changes to the DOM directly like you do here:
setTimeout(function() {
document.getElementById(component_id).disabled = false; // Change not tracked by Dash
}, 5000); // 5 seconds
What you could do instead is something like this:
from dash import Dash, html, dcc, ctx
from dash.dependencies import Output, Input
import dash_bootstrap_components as dbc
app = Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
app.layout = html.Div([
dcc.Interval(id="interval", interval=5000, disabled=True),
dbc.Button(
id="my-button",
children="Expand",
className="primary-btn mt-2 btn-small"
)
])
@app.callback(
Output("interval", "disabled"),
Output("my-button", "disabled"),
Input("interval", "n_intervals"),
Input("my-button", "n_clicks"),
prevent_initial_call=True
)
def toggle_expand_button(n_intervals, n_clicks):
enable_button = (True, False)
disable_button = (False, True)
if ctx.triggered_id == "interval":
return enable_button
return disable_button
if __name__ == "__main__":
app.run_server()
The idea above is to use an interval and make it disabled by default. Then when we click the button we make the button disabled but the interval enabled. Now the interval is enabled and it waits 5000
milliseconds after which the callback triggered again and we disable the interval and enable the button again.