Dropdown Options Not Updating

Below is a simplified example of the issue I am experiencing. I have two dropdowns, where the options for the second one depend on the choice on the first one. First a callback runs to populate the options of the first dropdown. This callback produces a dictionary with keys being the options for the first dropdown and values being lists containing the corresponding options for the second dropdown. The output of the callback is to store this dictionary on the page as well as populating the options for the first dropdown. Then when a user chooses an option on the first dropdown another callback runs which takes their choice as input and takes the previous dictionary as state. It finds the second dropdown options corresponding to the first dropdown user choice in the dictionary and then populates the second dropdown with these. The problem is that the second dropdown does not get populated with options. Despite this I have used print statements to verify that the second callback is running and its output is of the form that it should be for setting the “options” property of the second dropdown.

import dash
import dash_bootstrap_components as dbc
from dash import dcc
from dash import html
from dash.dependencies import Input, Output, State
import json


app = dash.Dash(
    external_stylesheets=[dbc.themes.BOOTSTRAP]
)

app.layout = html.Div([
    html.Button("Reload Course Options", id= "course_reset", style = {"margin-bottom":20}),
    html.Div(
        dcc.Dropdown(id = "course_select", placeholder = "Select Course"),
        style = {"width": "25%" }
    ),
    html.Div(
        dcc.Dropdown(id = "tee_select", placeholder = "Select Tees"),
        style = {"width": "25%" }
    ),
    html.Div(id = "course_options")
])
                

@app.callback(
    [Output(component_id = "course_select", component_property = "options"),
     Output(component_id = "course_options", component_property = "children")],
     Input(component_id = "course_reset", component_property = "n_clicks")
)
def load_courses(button):
    courses = {"Course":["Tee"]} 
    course_choices = [{"label": c, "value": c} for c in courses.keys()]
    return course_choices, json.dumps(courses)


@app.callback(
    Output(component_id = "tee_select", component_property = "options"),
    Input(component_id = "course_select", component_property = "value"),
    State(component_id = "course_options", component_property = "children")
)
def load_tees(course, all_courses):
    if course == None:
        return None
    else:
        tee_choices = [{"label": t, "value": t} for t in json.loads(all_courses)[course]]
        return tee_choices


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

Update: I have some kind of workaround by changing:

def load_tees(course, all_courses):
    if course == None:
        return None
    else:
        tee_choices = [{"label": t, "value": t} for t in json.loads(all_courses)[course]]
        return tee_choices

to:

def load_tees(course, all_courses):
    if course == None:
        return  [{"label": "", "value": ""}]
    else:
        tee_choices = [{"label": t, "value": t} for t in json.loads(all_courses)[course]]
        return tee_choices

However this is not really a solution as it leaves a blank option instead of no options.

It appears the solution is to return {} instead of None in the case the first case, that is:

def load_tees(course, all_courses):
    if course == None:
        return {}
    else:
        tee_choices = [{"label": t, "value": t} for t in json.loads(all_courses)[course]]
        return tee_choices

Hi @EricNeville welcome to the community

I think the best way is to use no_update to avoid any return when the value is None.

Like this, this use the syntaxis of last Dash version:

from dash.dash import no_update

And change the callback for:

@app.callback(
    Output(component_id = "tee_select", component_property = "options"),
    Input(component_id = "course_select", component_property = "value"),
    State(component_id = "course_options", component_property = "children")
)
def load_tees(course, all_courses):
    
    if course:
        tee_choices = [{"label": t, "value": t} for t in json.loads(all_courses)[course]]
        
        
        return tee_choices
    else:
        return no_update

Hi @Eduardo , I was thinking to try something like this preventing the update, however when course is cleared the secondary dropdown should also be cleared since it no longer has defined options. With this solution, if we set course, then set tee and then remove the course choice won’ t the tee choice remain?

One option could be to use a dcc.Div with an id instead of using the second dcc.Dropdown, and in the first callback build the second Dropdown.
Then every time the button is clicked the second Dropdown will have an empty option.

When the selection in the first drop down is empty, you should return an empty list, since you want zero options to be present. By returning None, you are probably causing a JS error (check the developer console).