Allow_duplicate in dash 2.9.1 vs 2.9.2

Hi,

After upgrading dash I am seeing some (for me) unexpected behavior. The below code works as intended on 2.9.1. However on >= 2.9.2 I get the following error:

In the callback for output(s):
button.disabled@a8b70adf69db29fef22e63e51b22165f
button.color@a8b70adf69db29fef22e63e51b22165f
Output 1 (button.color@a8b70adf69db29fef22e63e51b22165f) is already in use.
To resolve this, set allow_duplicate=True on
duplicate outputs, or combine the outputs into
one callback function, distinguishing the trigger
by using dash.callback_context if necessary.

I simply want a button greyed out and disabled whenever it’s clicked. Basically to make it not spammable.

I noticed there was a change to allow_duplicate in 2.9.2 in the changelog, but I am not dash savvy enough to know why this would affect my example. I hope somebody can come to my rescue!

This is the reproducible code. Try with dash 2.9.1, and then with 2.9.2.

import dash_bootstrap_components as dbc
from dash import html, dash
import time
from dash.dependencies import Input, Output

app = dash.Dash()

app.layout = html.Div([
    html.Div([
        dbc.Button("Button", id="button", color="danger",
                   style={"margin-top": "10px"})
    ], style={"float": "left"})
])


#disable ctc button in modal when clicked
@app.callback(Output("button", "disabled", allow_duplicate=True),
              Output("button", "color", allow_duplicate=True),
              Input("button", "n_clicks"),
              prevent_initial_call=True)
def disable_ctc_button(click):
    return [True, "secondary"]


#reenable ctc button after 10 seconds
@app.callback(Output("button", "disabled", allow_duplicate=True),
              Output("button", "color", allow_duplicate=True),
              Input("button", "n_clicks"),
              prevent_initial_call=True)
def reenable_ctc_button(click):
    time.sleep(10)
    return [False, "danger"]


if __name__ == '__main__':
    app.run_server(debug=True)
1 Like

Hey @sandalen94,

Welcome to the community!

I am far from being Dash savvy. Nevertheless, I have a solution for your problem : ) However, it might not be the optimal solution.

You can try dcc.Store component to share data between components, in your case between the same component. Think of it as a middle stop on your way to your destination (it is deactivating the button in your case).

Anyways, that’s how I would implement dcc.Store to solve your problem to your case:

import dash_bootstrap_components as dbc
from dash import html, dash
import time
from dash.dependencies import Input, Output,State
import dash_core_components as dcc

app = dash.Dash()

app.layout = html.Div([
    html.Div([
        dbc.Button("Button", id="button", color="danger",
                   style={"margin-top": "10px"}),
        dcc.Store(id='intermediate-value')
    ], style={"float": "left"})
])


#disable ctc button in modal when clicked
@app.callback(Output("button", "disabled",allow_duplicate=True),
              Output("button", "color",allow_duplicate=True),
              Output("intermediate-value","data"),
              Input("button", "n_clicks"),
              prevent_initial_call=True)
def disable_ctc_button(click):
    return [True, "secondary","deactivate"]


#reenable ctc button after 10 seconds
@app.callback(Output("button", "disabled",allow_duplicate=True),
              Output("button", "color",allow_duplicate=True),
              Input("intermediate-value","data"),
              prevent_initial_call=True)
def reenable_ctc_button(status):
    time.sleep(10)
    return [False, "danger"]


if __name__ == '__main__':
    app.run_server(debug=True)

Do not forget to import dash_core_components !

Cheers!

2 Likes

Thanks Berbere,

Your solution works and is probably better practice than what I am trying to do since I am relying on the order of the callbacks.

I’ve implemented your method just with a html.Div instead of a dcc.Store.

However, if anyone knows why the version upgrade causes this change in behaviour, I would be very interested in hearing about it!

Hello @sandalen94,

This is an issue with how dash serializes the callback to be determined as unique. It does this by using the input components, since your inputs are exactly the same, then these two unique identifiers are exactly the same. This makes it impossible to know which one is supposed to be triggered.

As far as the code above, you don’t have to import from dependencies, or dash_core_components, all of these are available by in dash now:

from dash import Dash, Input, Output, dcc, html, no_update, Patch…
1 Like

You could try adding a State() of a component to one of those callbacks even if you don’t need it, maybe a style of a div.

Not sure if that’s going to help, though

Thanks jinnyzor.

It has to be an Input, the State doesn’t get used in the serialization.

You could use the id as an input and just don’t use it.

1 Like

Good to know. I think I’ve seen this in one of your code snippets around here, @jinnyzor

1 Like