Reputation: 35
I have a dash program that creates one to three buttons based on user input in a dropdown. The user can then click the button and the program will tell them which button was last pressed. Like so:
However an issue arises whenever I select a dropdown value that doesn't create all 3 buttons. I can select 1 or 2, and 1 or 2 buttons are created, but trying to click button-1 or button-2 returns an error and does not properly run the callback.
The error I receive is: A nonexistent object was used in an `Input` of a Dash callback. The id of this object is `button-2` and the property is `n_clicks`. The string ids in the current layout are: [page-content, dropdown, button-row, last-selected, button-0, button-1]
I understand that this is occurring because my callback takes all three buttons as an input despite the fact that all three buttons might not exist when the callback is run, but I am unsure how to fix this issue.
Here is the code:
import dash
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
from dash import Dash, dcc, html, Input, Output, callback
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.ZEPHYR], suppress_callback_exceptions=True)
server = app.server
# Layout for the form
layout = dbc.Container(
[
dbc.Row( children =
[
html.Strong('How many buttons do you want?', style={'font-size': '20px', 'width': 'auto'}),
dcc.Dropdown([1, 2, 3], id='dropdown'),
], justify='center', align='center', style={'margin-top': '20px'}
)
]
)
buttons = html.Div(
[
dbc.Col(
[
dbc.Row([], id='button-row'),
html.Div([],id='last-selected')
]
)
]
)
app.layout = html.Div(
[
html.Div(children = [layout, buttons], id='page-content'),
]
)
@callback(Output('button-row', 'children'),
Input('dropdown', 'value'))
def update_button_row(dropdown):
children = []
for each in range(dropdown):
children.append(dbc.Button('Button {}'.format(each), id='button-{}'.format(each)))
return children
@callback(Output('last-selected', 'children'),
Input('button-0', 'n_clicks'),
Input('button-1', 'n_clicks'),
Input('button-2', 'n_clicks'),
prevent_initial_call = True)
def update_last_selected(button0, button1, button2):
ctx = dash.callback_context
if not ctx.triggered:
return ''
else:
button_id = ctx.triggered[0]['prop_id'].split('.')[0]
return 'Last selected: {}'.format(button_id)
# Launch the app with debug mode on
if __name__ == '__main__':
app.run_server(debug=True)
I'd like a solution that allows all buttons created by the update-button-row function to have the ability to print their name in the 'last-selected' div when they have been clicked.
Thank you for reading!
Upvotes: 2
Views: 440
Reputation: 1275
You can use Pattern-Matching Callbacks and modify your callbacks to fit the first example from the docs:
from dash import ALL
@callback(Output('button-row', 'children'),
Input('dropdown', 'value'))
def update_button_row(dropdown):
children = []
for each in range(dropdown):
button_id = {
"type": "button",
"index": "button-{}".format(each)
}
children.append(dbc.Button('Button {}'.format(each), id=button_id))
return children
@callback(Output('last-selected', 'children'),
Input({"type": "button", "index": ALL}, "n_clicks"),
prevent_initial_call = True)
def update_last_selected(n_clicks):
ctx = dash.callback_context
if not ctx.triggered:
return ''
else:
button_id = ctx.triggered[0]['prop_id'].split('.')[0] # This will return a stringified dictionary of the above defined button id
return 'Last selected: {}'.format(button_id)
Note that the id
property in the first callback has changed to a dictionary. This dictionary will now be returned by the second callback (and can easily be parsed with json.loads(button_id)
to get the desired output).
Upvotes: 1