Best way to update a single component from multiple pattern matching components

I see 85 on the screen after clicking the button twice (after first click, n_clicks is shown), which aligns perfectly with the print statements. That’s not what you are seeing?

click on the top button #btn only (not the row of buttons)
Also the prints are one off

add button 3
func 2

Ah, now I think that I understand what you mean. TLDR; it’s not a bug. It’s a feature :wink:

Let’s denotes the two callbacks a (add_button) and b (func), and the output element X. So, what you want is a structure like,

a(..) -> X
b(..) -> X

Due to complexities that can arise in building a DAG for this kind of dependency, Dash doesn’t support it. A key issue is the ambiguity when a and b are triggered by the same input; which one should populate X? The multiplexer applies a trick to circumvent the “problem” by remapping the callbacks into,

a(..) -> Y
b(..) -> Z
c(Y,Z) -> X

where c is a new intermediate callback, while Y and Z are intermediate elements. This remapping thus removes the complexity, but does so by making a choice on what value to propagate via the definiton of the remapping function c. Note that it doesn’t make any gurantee on this choice - and the “unexpected behavior” you are seeing, is because c doesn’t make the choice that you intended.

In most cases this choice doesn’t matter (I think this is the first the anyone has raised an issue on it, and the multiplexer is pretty widely used). At the moment, the choice is simply made as the first element appearing in the list of triggers from the dash callback context. I was thinking about making it an option to “prefer” one callback over another at some point (in your case, setting the priority of b greater than a would yield the behaviour you expect), but I never needed it myself - and I didn’t see any request for it either, so I never implemented it.

1 Like

@jinnyzor :grin:

That’s my favorite, made me chuckle.

2 Likes

Made me laugh too…but doesn’t help me :joy::sob:

1 Like

Maybe I’ll swap my order to force it to do what I want even though it doesn’t behave as I would have thought. Thanks @Emil for the detailed answer. Glad I’m the first person with this use case. I was just trying to circumvent some other problems and thought this would work. I’ll try get it working

I am not sure there is anything that you can do (except for patching Dash and/or dash-extensions). What you need to change is not the order in which the callbacks are defined, but the order in which they appear in the triggered list in the dash callback context. And I am not sure how this order is controlled - or if the order is guaranteed at all. The proper solution for your usecase would be the “callback priority” feature.

I have drafted a solution and pushed it as dash-extensions==0.1.8rc2. It’s simply a new priority keyword. If you set it, the callback with the highest priority will take precedence (and default priority is 0). Hence, to make your example work as you intend, you should set the priority of a_func to 1 (or higher),

@app.callback(
    Output("store", "data"),
    Output("txt", "children"),
    Input({"type": "btn-group", "index": ALL}, "n_clicks"),
    State("store", "data"),
    prevent_initial_call=True,
    priority=1
)
def a_func(click, data):
    print(f"func {data}")
    return 85, f"Last clicked id = {data}"

Can you test if it works as intended for your usecases?

This still is not behaving as I expected. If you uncomment the line of code, and then comment out the first priority, it always returns None

import flask
from dash import State, ALL, dcc, no_update, html
from dash_extensions.enrich import Output, DashProxy, Input, MultiplexerTransform

f_app = flask.Flask(__name__)


app = DashProxy(transforms=[MultiplexerTransform()], server=f_app)

app.layout = html.Div(
    children=[
        html.Button("Click", id="btn", n_clicks=0),
        html.Div([], id="btn-box", style={"display": "flex", "gap": "5px"}),
        html.Div("None", id="txt"),
        dcc.Store("store", data=0, storage_type="local", clear_data=True),
    ]
)


@app.callback(
    Output("store", "data"),
    Output("btn-box", "children"),
    Input("btn", "n_clicks"),
    State("btn-box", "children"),
    priority=1,
    prevent_initial_call=True,
)
def add_button(id, children):
    children.append(
        html.Button(f"Button {id}", id={"type": "btn-group", "index": id}, n_clicks=0)
    )
    print(f"add button {id}")

    return id, children


@app.callback(
    Output("txt", "children"),
    Output("store", "data"),
    Input({"type": "btn-group", "index": ALL}, "n_clicks"),
    State("store", "data"),
    # priority=1,
    prevent_initial_call=True,
)
def func(click, data):
    print(f"func {data}")
    return f"Last clicked id = {data}", data


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

Yes, that is the expected behaviour. When you set the priority of the second callback highest, the first callback will never be able to populate the store, i.e. data will remain None.

I someone would like a flow that the first callback sets store to value 1, when the second callback gets fired, it then has access to this value in the store via state. but currently the best i can do is to be 1 off, there is always the previous value, it misses that store update and the state uses the value prior to this