Help with a dropdown related bug

Hi, I’m creating a dashboard with dependent dropdowns (i.e one drop down populates the options of another).

The issue is that the callback of one dropdown ends up changing the other dropdown. It is the strangest thing. My source is a bit too long, so I recreated this issue in a very simple self-contained app. If you run the app, do this:

  1. Change the cities
  2. Change the gender. You will notice that the cities dropdown changes.

another example:

  1. Change the name
  2. Change the country. You will notice that the name changes.

Please let me know if this is something I’m doing incorrectly or if this could be a possible bug.

Thanks.

Simple app:

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

countries = ['America', 'Canada']
cities = {
    'America': ['San Francisco', 'NYC'],
    'Canada': ['Montreal', 'Toronto']
}

gender = ['Male', 'Female']
names = {
    'Male': ['John', 'Bill'],
    'Female': ['Ashley', 'Amanda']
}
app = dash.Dash(__name__)

app.layout = html.Div(children=[
    html.P('Countries'),
    dcc.Dropdown(
        id='countries-dropdown',
        options=[{'label': i, 'value': i} for i in countries],
        value='America',
    ),
    html.P('Cities (dependent on Countries)'),
    dcc.Dropdown(
        id='cities-dropdown',
        options=[{'label': i, 'value': i} for i in cities['America']],
        value='San Francisco'
    ),
    html.P('Gender'),
    dcc.Dropdown(
        id='gender-dropdown',
        options=[{'label': i, 'value': i} for i in gender],
        value='Male'
    ),
    html.P('Names (dependent on Gender)'),
    dcc.Dropdown(
        id='names-dropdown',
        options=[{'label': i, 'value': i} for i in names['Male']],
        value='John'
    )
])

@app.callback(Output('cities-dropdown', 'options'),
    [Input('countries-dropdown', 'value')])
def update_dropdown_options(country):
        return [{'label': i, 'value': i} for i in cities[country]]

@app.callback(Output('cities-dropdown', 'value'),
    [Input('countries-dropdown', 'value')])
def update_dropdown_values(country):
        return cities[country][0]

@app.callback(Output('names-dropdown', 'options'),
    [Input('gender-dropdown', 'value')])
def update_dropdown_options(gender):
        return [{'label': i, 'value': i} for i in names[gender]]

@app.callback(Output('names-dropdown', 'value'),
    [Input('gender-dropdown', 'value')])
def update_dropdown_values(gender):
        return names[gender][0]

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

You can fix the issue if you change input for the callbacks that set the value of the dropdowns to Input('cities-dropdown', 'options') and then add in the country value as a State. This creates a sequential chain of callbacks that will give you more predictable behaviour than applying two effects on one change as you are doing above (since dash supports multiple threads and you can’t really tell which one will complete first).

from dash.dependencies import State

@app.callback(Output('cities-dropdown', 'value'),
    [Input('cities-dropdown', 'options')],
    [State('countries-dropdown', 'value')])
def update_dropdown_values(_, country):
        return cities[country][0]
1 Like

Thanks @michaelbabyn, this was really tripping me up!

@chriddyp, I notice that dash gives helpful pointers in the dash.exceptions.NonExistantIdException module. Is there any exception that get’s raised for situations like this?