Multiple callbacks with each the same input and output, but different states

Hi!
In my app I have a dcc.Tabs component with multiple dcc.Tab which each have multiple different inputs of different types (dropdowns, text inputs, etc.).
Outside of the tabs is a button, which takes the inputs of the current tab, does some operations with it and sends it to a store.
However, since the inputs of each tab is unique and the operations to treat each set of inputs in also unique to a tab, I have created a callback for each tab.
The problem is that each of these callback share the same input (the button outside the tabs) and the same output (the store).
So, for n tabs and n callbacks, I get a n-1 Duplicate callback outputs errors for the store component.
I get that the Dash is preventing me from executing identical callbacks, but I made sure in the callbacks to include a state which ensure that if the wrong callback is called, the callback returns no_update.

Here a simplified example of my code:

from dash import Dash, Output, Input, State, dcc, no_update
import dash_bootstrap_components as dbc

app = Dash(
    __name__, 
    suppress_callback_exceptions=True, 
    prevent_initial_callbacks="initial_duplicate"
    )

app.layout = dbc.Container([
    dcc.Tabs([
        dcc.Tab(value="tab1", children=[dcc.Input(id="inp1")]),
        dcc.Tab(value="tab2", children=[dcc.Input(id="inp2")]),
        dcc.Tab(value="tab3", children=[dcc.Input(id="inp3")]),
    ], id="tabs"),
    dbc.Button("Confirm", id="confirm_btn"),
    dcc.Store(id="store")
])

@app.callback(Output("store", "data", allow_duplicate=True),
              Input("confirm_btn", "n_clicks"),
              State("inp1", "value"),
              State("tabs", "value"),
              prevent_initial_call=True)
def store_input_1_value(n_clicks, value1, tab):
    if not (n_clicks and value1 and tab == "tab1"):
        return no_update
    # Do some operatations with value1 and return it to the store.
    return value1

@app.callback(Output("store", "data", allow_duplicate=True),
              Input("confirm_btn", "n_clicks"),
              State("inp2", "value"),
              State("tabs", "value"),
              prevent_initial_call=True)
def store_input_2_value(n_clicks, value2, tab):
    if not (n_clicks and value2 and tab == "tab2"):
        return no_update
    # Do some different operatations with value3 and return it to the store.
    return value2

@app.callback(Output("store", "data", allow_duplicate=True),
              Input("confirm_btn", "n_clicks"),
              State("inp3", "value"),
              State("tabs", "value"),
              prevent_initial_call=True)
def store_input_3_value(n_clicks, value3, tab):
    if not (n_clicks and value3 and tab == "tab2"):
        return no_update
    # Do some very different operatations with value2 and return it to the store.
    return value3

Is there a workaround to this?

I have managed to make it work with up to 3 callbacks by using n_clicks_timestamp instead of n_clicks on one Input and also not using allow_duplicate=True on the last callback, like such:

@app.callback(Output("store", "data", allow_duplicate=True),
              Input("confirm_btn", "n_clicks"),
              State("inp1", "value"),
              State("tabs", "value"),
              prevent_initial_call=True)
def store_input_1_value(n_clicks, value1, tab):
    ...

@app.callback(Output("store", "data", allow_duplicate=True),
              Input("confirm_btn", "n_clicks_timestamp"),
              State("inp2", "value"),
              State("tabs", "value"),
              prevent_initial_call=True)
def store_input_2_value(n_clicks, value2, tab):
    ...

@app.callback(Output("store", "data"),
              Input("confirm_btn", "n_clicks"),
              State("inp3", "value"),
              State("tabs", "value"),
              prevent_initial_call=True)
def store_input_3_value(n_clicks, value3, tab):
    ...

but I would need to have more than 3 tabs/callbacks.
And merging my callbacks together and using ctx is not really an option since the callbacks are very long and already have 6-7 State each.
I believe I could use pattern-matching to simplify all the State, but it still would be pretty complicated.

Thank you,
Thomas

Hello @tcharland13,

Callbacks are given a unique identifier based upon the inputs, not different states, to it. Even if you denote that it is a duplicate, this would still cause issues due to the inputs causing the same unique identifier.

Assuming that your real code isnt much more complex than this one, you should be able to use just 1 callback for this, and not have multiple.

Hi! Thanks for the answer. Sadly my real code is more complicated. Well is not really the functions themselves (~50 line each) but more the number of states, as each callback has between 8 and 12 states, most of which differ between the callbacks as they refer to the content of the associated tab.

Do you happen to know why it is possible to have two callbacks share the same input and output if one of them has allow_duplicate=True? It seems weird, according to what you just told me.

As in:

@app.callback(Output("store", "data", allow_duplicate=True),
              Input("confirm_btn", "n_clicks"),
              State("inp1", "value"),
              State("tabs", "value"),
              prevent_initial_call=True)
def store_input_1_value(n_clicks, value1, tab):
    ...

@app.callback(Output("store", "data"),
              Input("confirm_btn", "n_clicks"),
              State("inp3", "value"),
              State("tabs", "value"),
              prevent_initial_call=True)
def store_input_3_value(n_clicks, value3, tab):
    ...

Thanks

1 Like

Its how the unique identifier is determined. On both the above callbacks, the identifier would be the same.

One thing I do is have an unused input from inp1 id. This allows it to be unique across the board, and then you wont have an issue. :slight_smile:

2 Likes

Ah yes of course! Thanks a lot!

1 Like

Nice, a dummy input. Don’t forget to comment why this input is there, otherwise you’re going to ask yourself this very question in half a year :rofl:

I would aks myself for sure :rofl: :rofl:

2 Likes