Django, Dash, multiple buttons don't work

Hi there!

When my App has more than one Button with a callback, the callbacks are no longer fired:

app = DjangoDash('Frontend')
app.layout = Div([
    Button('Load data', id='btn_loaddata', style=flex1_style),
    Button('Test', id='btn_test', style=flex1_style),
    Label('Output', id='lbl_test'),
])

@app.callback(
    Output('lbl_test', 'value'),
    Input('btn_loaddata', 'n_clicks'),
    prevent_initial_call=True
)
def btn_loaddata_clicked(n_clicks):
    print('reached loaddata')
    return 'foobar'

@app.callback(
    Output('lbl_test', 'value'),
    Input('btn_test', 'n_clicks'),
    prevent_initial_call=True
)
def btn_test_clicked(n_clicks):
    print('reached test')
    return 'foobar'

When I comment out one of the callbacks, everything works as expected. Removing prevent_initial_call or passing an empty list as Output doesn’t change the behaviour.

What’s the issue here?

The outputs are the same with no allow_duplicate=True prop within them. The issue is with the Output('lbl_test', 'value'),

app = DjangoDash('Frontend')
app.layout = Div([
    Button('Load data', id='btn_loaddata', style=flex1_style),
    Button('Test', id='btn_test', style=flex1_style),
    Label('Output', id='lbl_test'),
])

@app.callback(
    Output('lbl_test', 'value'),
    Input('btn_loaddata', 'n_clicks'),
    prevent_initial_call=True
)
def btn_loaddata_clicked(n_clicks):
    print('reached loaddata')
    return 'foobar'

@app.callback(
    Output('lbl_test', 'value', allow_duplicate=True), # added allow_duplicate=True to have two callbacks pointing to the same 
                                                                                         # output
    Input('btn_test', 'n_clicks'),
    prevent_initial_call=True
)
def btn_test_clicked(n_clicks):
    print('reached test')
    return 'foobar'

ideally you want to limit redundant callbacks you could simplify that whole application with:

from dash import Input, Output, html, dcc, ctx # Make sure to import ctx
from django_plotly_dash import DjangoDash

# Assuming flex1_style is defined elsewhere, e.g.:
flex1_style = {'margin': '5px', 'flex': '1'}

app = DjangoDash('Frontend')

app.layout = html.Div([
    html.Button('Load data', id='btn_loaddata', style=flex1_style),
    html.Button('Test', id='btn_test', style=flex1_style),
    html.Label('Output', id='lbl_test'), # dcc.Label or html.Label, ensure 'value' or 'children' is appropriate
                                      # For html.Label, 'children' is typically used to set text.
                                      # If you intend to use a component with a 'value' property,
                                      # ensure html.Label supports it or use dcc.Input/dcc.Textarea disabled, for example.
                                      # For simplicity, I'll assume 'children' for html.Label is intended.
])

@app.callback(
    Output('lbl_test', 'children'),  # Changed to 'children' for html.Label; use 'value' if it's a different component type
    Input('btn_loaddata', 'n_clicks'),
    Input('btn_test', 'n_clicks'),
    prevent_initial_call=True
)
def unified_button_callback(n_clicks_load, n_clicks_test):
    triggered_id = ctx.triggered_id
    # Using dash.ctx.triggered_id to know which input fired

    if triggered_id == 'btn_loaddata':
        print('reached loaddata')
        # Perform data loading operations here
        return 'Data loaded via btn_loaddata'
    elif triggered_id == 'btn_test':
        print('reached test')
        # Perform test operations here
        return 'Test executed via btn_test'
    return "No button clicked or an unexpected trigger" # Default or error message

2 Likes

Wonderful, works like a charm!
And the trick with dash.ctx.triggered_id you answered before I could ask!
Thank you so much!