MultiplexerTransform is not working with dash==2.9.0 OR dash_extensions==0.1.13

Hello everybody!

So, I’ve been trying to make one of the most basic things you may imagine doing on your website – user clicks on a button, some computations go on, disable the button and when the result is over turn it on again. Plain and simple, but put me into the pit of despair – moreover, I bet I’ll be short of a handful of hair after this task.

I am trying to use the MultiplexerTransform from dash_extensions, but for some reason Outputs of callbacks are not being updated, even though the corresponding callbacks ARE getting fired up for sure.

Here is a little demonstration:

from time import sleep

from dash_extensions.enrich import (
    DashProxy, 
    Input, 
    Output, 
    State, 
    MultiplexerTransform,
    NoOutputTransform,
    html,
    dcc,
    callback_context,
)


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

button      = html.Button('Button', id='download_button')
dropdown    = dcc.Dropdown(['One', 'Two', 'Three'], 'One', id = 'choise_drop')
dummy       = dcc.Store(id='dummy')
trigger     = dcc.Store(id='trigger')

app.layout = html.Div(
    children=[
        button, 
        dropdown, 
        dummy, 
        trigger,
    ],
    id='main',
)

@app.callback(
    Output('dummy', 'data'),
    Input('choise_drop', 'value'),
    prevent_initial_call=True,
)
def select(sel_opt):
    print('GRAPH CHOISE')


@app.callback(
    Output('choise_drop', 'disabled'),
    Output('download_button', 'disabled'),
    Input('trigger', 'data'),
    prevent_initial_call=True,
)
def download(trig_data):
    print('TRIGGER')
    sleep(5)
    return False, False 


@app.callback(
    Output('choise_drop', 'disabled'),
    Output('download_button', 'disabled'),
    Output('trigger', 'data'),
    Input('download_button', 'n_clicks'),
    State('choise_drop', 'value'),
    prevent_initial_call=True,
)
def download_button(n_clicks, sel_opt):
    print('BUTTON')
    return True, True, True


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

Here, if I delete the Outputs of download callback, the button and dropdown are disabled properly. But if in contrast I leave these Outputs only in this callback and delete them from the download_button nothing works, similarly to the full solution.

Am I doing something wrong or it is just a bug?

PS.
Yes, I’ve been trying to make this happen without extensions via dedicated callback that would check whether that was a button fired, or a State triggered by the button after the computation is over (the trick with context like one good fellah described here), but the order of callbacks is not guaranteed, so I was left in the situation where buttons were not enabled at all.

Hello @tntnkn,

Welcome to the community!

Are you just using dash_extensions to allow you to have duplicate outputs? If so, I believe that Dash 2.9 allows this functionality.

@jinnyzor question might be: with the changes in 2.9.x are the extensions still working as before or are they mutually exclusive

@tntnkn,

Can you show your code from when you tried not to use extensions?

Hey, thank you!

Yes, this is the goal.
I’m not sure that you are correct here – my attempts of using duplicate Outputs without extensions were of no avail. Plus, I have a feeling that I’ve seen the docs saying the dups are not allowed.

I don’t think it has been updated in the documentation, but I was thinking it was available with this release…

@adamschroeder, can you confirm?

I’ll also try in a little bit, didn’t realize it was released yesterday as I didn’t see an announcement yet. Haha

Sure. That was a first solution without duplicate Outputs, the one that I mentioned in the PS section of the question.

I am replicating here, but the code fails in a similar fashion:

from time import sleep

from dash import (
    Dash, 
    Input, 
    Output, 
    State, 
    html,
    dcc,
    callback_context,
)


app = Dash(__name__)

button      = html.Button('Button', id='download_button')
dropdown    = dcc.Dropdown(['One', 'Two', 'Three'], 'One', id = 'choise_drop')
dummy       = dcc.Store(id='dummy')
trigger     = dcc.Store(id='trigger')

app.layout = html.Div(
    children=[
        button, 
        dropdown, 
        dummy, 
        trigger,
    ],
    id='main',
)


@app.callback(
    Output('dummy', 'data'),
    Input('choise_drop', 'value'),
    prevent_initial_call=True,
)
def select(sel_opt):
    print('GRAPH CHOISE')

@app.callback(
    Output('choise_drop', 'disabled'),
    Output('download_button', 'disabled'),
    Input('trigger', 'data'),
    Input('download_button', 'n_clicks'),
    prevent_initial_call=True,
)
def disable_control(trig_data, n_clicks):
    print('CONTROLOS')
    context = callback_context.triggered[0]['prop_id'].split('.')[0]

    if context == 'download_button':
        return True, True
    else:
        return False, False

@app.callback(
    Output('trigger', 'data'),
    Input('download_button', 'n_clicks'),
    State('choise_drop', 'value'),
    prevent_initial_call=True,
)
def download_button(n_clicks, sel_opt):
    print('BUTTON')
    sleep(5)
    return True


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

As for duplicates in Dash>= 2.9.0 – it is not working for sure. I have a version 2.9.0, and the following code gives duplication errors:

from time import sleep

from dash import (
    Dash, 
    Input, 
    Output, 
    State, 
    html,
    dcc,
    callback_context,
)

app = Dash(__name__)

button      = html.Button('Button', id='download_button')
dropdown    = dcc.Dropdown(['One', 'Two', 'Three'], 'One', id = 'choise_drop')
dummy       = dcc.Store(id='dummy')
trigger     = dcc.Store(id='trigger')

app.layout = html.Div(
    children=[
        button, 
        dropdown, 
        dummy, 
        trigger,
    ],
    id='main',
)

@app.callback(
    Output('dummy', 'data'),
    Input('choise_drop', 'value'),
    prevent_initial_call=True,
)
def select(sel_opt):
    print('GRAPH CHOISE')


@app.callback(
    Output('choise_drop', 'disabled'),
    Output('download_button', 'disabled'),
    Input('trigger', 'data'),
    prevent_initial_call=True,
)
def download(trig_data):
    print('TRIGGER')
    sleep(5)
    return False, False 


@app.callback(
    Output('choise_drop', 'disabled'),
    Output('download_button', 'disabled'),
    Output('trigger', 'data'),
    Input('download_button', 'n_clicks'),
    State('choise_drop', 'value'),
    prevent_initial_call=True,
)
def download_button(n_clicks, sel_opt):
    print('BUTTON')
    return True, True, True


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

I am confident that you have to declare the outputs.

Inside the output definition, try setting allow_duplicate=True.

So, something like this:

Output(‘choise_drop’, ‘disabled’, allow_duplicate=True)

On the secondary output.

3 Likes

Well, now we are talking! Kind Sir, you shall be remembered to the posterity as a gentleman of a great wit.

I’ve set what you’ve said in both Ouputs changing disabled properties in download_button callback, and everything got fixed.

So, this is a conflict between dash and dash_extensions functionalities?

2 Likes

My guess is that with the changes that Dash made, extensions doesn’t perform the same way, at least in this matter.

Until @Emil can get it working, to use this same functionality in extensions, you’ll have to use 2.8.*. :grin:

Well, turns out I had everything I needed with a plain Dash, so hope extensions will have a better chance some other day =)

By the way, may I have a slightly off-topic, but related question? It seems for me that this kind of basic feature (disabling components until computation is finished) is done with a wee bit of over-engeneering. Is this I who missed the established good solution, or Dash itself is not a right tool?

To disable a component during a callback, you should use a background callback. This can disable something during runtime. Or you can define any number of things during runtime.

Scroll down to the part about disabling buttons.

As far as disabling natively, there are some things that do this, but I’m not sure you want to go down that path. For example, say you wanted to change something about the info you just submitted, you wouldn’t be able to do that until the response was done.

So, it’s better to allow the developer, you, to make those kinds of UI/UX decisions.

UI - user interface
UX - user experience

1 Like

So, I indeed missed a good solution =)

Thank you once again for your time and help – it was of a great assistance to me!

1 Like

Thanks for the solution, Bryan.

yes, to confirm, duplicate output was released yesterday with Dash v2.9

The documentation on the matter has been written and should be released today or early next week.

I will also release an announcement post early next week.

I’m glad you got it to work, as we’re excited to see our community try it out.

1 Like

@tntnkn
Updated Docs below. Full post to come next week.

4 Likes

Hey, thank you for sharing the docs. Partial updates seem to be a major advancement, should definitely check it out later!

Could you clarify one thing, please, just to be sure – allow_duplicate=True can be set on a duplicate Output in a single callback only? So, if I have three callbacks with the same Output, I need to set it only on one of them picked randomly?

I believe you have to set it on the other two. Or you can set it to all three.

This is just a caveat to let you know that the order might not update according to what you are expecting because another one of your callbacks may be updating it at the same time.

1 Like