How to separate parallel/concurrent callbacks (nested elements) in Dash 1.13+?

Hi,

if there are nested html elements with callbacks (e.g. a button inside a div and both have callbacks), how do I detect which one fired?

Minimal working example:

import dash
import dash_html_components as html
from dash.dependencies import Input, Output

app = dash.Dash()
app.layout = html.Div(id="div", style={"background": "yellow", "padding": "200px"}, children=[
    html.Button(id="button", children="show infotext"),
    html.Div(id="info", children="INFOTEXT", style={"display": "none"})
])


@app.callback(
    Output("info", "style"),
    [Input("button", "n_clicks"),
     Input("div", "n_clicks")])
def test(btn, div):
    ctx = dash.callback_context
    print("Callback", ctx.triggered)
    prop_ids = [x['prop_id'].split('.')[0] for x in ctx.triggered]
    if "button" in prop_ids:
        print("Action: show")
        return {"display": "block"}
    else:
        print("Action: hide")
        return {"display": "none"}

app.run_server(debug=True)

When the button is pressed, I want to show the info box. When the background is clicked, I want to hide the infobox. Simple. This code would have worked in Dash 1.12, because back then, the callback was executed once only and prop_ids was a list of all callbacks ([“button”, “div”] when clicking on the button and [“div”] when clicking on the div).

However, since 1.13, dash behaves differently. The callback function is now executed twice, one time for the button callback and one time for the div. This makes it impossible to figure out which one was actually pressed.

Example output for the above code after clicking one time on the button (the None callback is just the initial callback on load an can be ignored):

Callback [{'prop_id': '.', 'value': None}]
Action: hide
Callback [{'prop_id': 'button.n_clicks', 'value': 1}]
Action: show
Callback [{'prop_id': 'div.n_clicks', 'value': 1}]
Action: hide

So, the button callback is executed, the infobox is displayed, but then the div div callback always hides the infobox.

How can I get around this?

Hi @Michael1

I ran your sample code, and I’m not sure I understand your question. It seems to work as you describe: If the button is clicked the “INFOTEXT” is displayed, and if the background is clicked, it’s hidden. Did you want to do something different?

If you are trying to isolate only which input id triggers a callback you can do this:

input_id = ctx.triggered[0]['prop_id'].split('.')[0]
if input_id == 'button':
    dosomething....
if input_id == 'div':
     dosomethingelse....

Hi @AnnMarieW,
which version of Dash did you use?
What you describe is precisley the behaviour I am looking for. My code above behaves that way in Dash 1.12. However, when I switch to Dash 1.13 this is no longer the case and the infobox is never displayed.
I am looking for how to get the 1.12 behaviour in 1.13+

Hi @Michael1

You are right, I thought I was running 1.13, but I was on 1.12. I see what you mean and it doesn’t work in 1.14 either. When you click on the button, it triggers the button id first, then the div id immediately after.

This might be a question for the nice folks at Plotly.

I opened an issue here.