Black Lives Matter. Please consider donating to Black Girls Code today.
Learn how to use COVID-19 data in open source Dash apps. Register for the Sept 23rd webinar with IQT!

How to disable Dropdown Menus with multiple callbacks

Say we have to filter on a tree-like data source, i.e. Country -> State -> Province. Each of the three categories has its own dropdown menu with some placeholder text.

At first run, the States and Provinces lists are empty. When a Country is chosen, States are populated, and when a State is chosen, Provinces are populated.

Now that a Province has been chosen, when the user selects another Country, Provinces and States should be reset to an initial state, with the placeholder text.

How can this be done? A callback signature for the Countries list that clears the Provinces list would look like:

@callback(Output('provinces-list', 'options'), [Input('countries-list', 'n_clicks')])
def clear_provinces_list(): [... clear the provinces-list component here]

But a callback signature for selecting from the States list would be

@callback(Output('provinces-list', 'options'), [Input('states-list', 'n_clicks')])
def populate_provinces_list(): [... populate the provinces-list component here]

This results in a message

You have already assigned a callback to the output
with ID “provinces-list” and property “options”. An output can only have
a single callback function. Try combining your inputs and
callback functions together into one function.

This may have been addressed before, but I don’t understand what it means to “combine inputs and callbacks” into a single function…

@alex_01 - There is an example on these nested dropdowns towards the end of the tutorial here: https://plot.ly/dash/getting-started-part-2:

# -*- coding: utf-8 -*-
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash(__name__)

all_options = {
    'America': ['New York City', 'San Francisco', 'Cincinnati'],
    'Canada': [u'Montréal', 'Toronto', 'Ottawa']
}
app.layout = html.Div([
    dcc.RadioItems(
        id='countries-dropdown',
        options=[{'label': k, 'value': k} for k in all_options.keys()],
        value='America'
    ),

    html.Hr(),

    dcc.RadioItems(id='cities-dropdown'),

    html.Hr(),

    html.Div(id='display-selected-values')
])

@app.callback(
    dash.dependencies.Output('cities-dropdown', 'options'),
    [dash.dependencies.Input('countries-dropdown', 'value')])
def set_cities_options(selected_country):
    return [{'label': i, 'value': i} for i in all_options[selected_country]]

@app.callback(
    dash.dependencies.Output('cities-dropdown', 'value'),
    [dash.dependencies.Input('cities-dropdown', 'options')])
def set_cities_value(available_options):
    return available_options[0]['value']

@app.callback(
    dash.dependencies.Output('display-selected-values', 'children'),
    [dash.dependencies.Input('countries-dropdown', 'value'),
     dash.dependencies.Input('cities-dropdown', 'value')])
def set_display_children(selected_country, selected_city):
    return u'{} is a city in {}'.format(
        selected_city, selected_country,
    )

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

In this example, we’re setting the first available option when you change countries, but you could also clear the option by returning None instead of available_options[0]['value']

So when a new country is selected, have the second drop-down set to None. In addition, the set_display_children callback should also return an empty string when a new country is selected.

The only way I figure this out is to do add a “special default value” to the all_options dict. In this case, None is convenient, since it doesn’t show up in a dropdown menu (although it does show up if your component is a RadioItem).

all_options = {
    'America': [None, 'New York City', 'San Francisco', 'Cincinnati'],
    'Canada': [None, u'Montréal', 'Toronto', 'Ottawa']
}

and adding this line to the set_display_children callback:

def set_display_children(selected_country, selected_city):
    if not selected_city:
        return
    [...]

It works okay for a dropdown menu, but is there a better way? As mentioned above, a blank radiobutton shows up using this method. Is there a property of a radiobutton such as {'visible': 'false'}? I couldn’t find such a property in the docs.

Hmm I wonder if this question should have been posed in a way similar to “reset” button post: Reset to Initial state without reloading page

As we are making new selections for data to plot, we shouldn’t be able to see the older selections which are no longer valid.

I don’t think that I follow. By changing def set_cities_value to return None, this clears the value of the RadioItems. Is this what you wanted or something else?

dash-multiple-radio-items-clear

Full code:

# -*- coding: utf-8 -*-
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash(__name__)

all_options = {
    'America': ['New York City', 'San Francisco', 'Cincinnati'],
    'Canada': [u'Montréal', 'Toronto', 'Ottawa']
}
app.layout = html.Div([
    dcc.RadioItems(
        id='countries-dropdown',
        options=[{'label': k, 'value': k} for k in all_options.keys()],
        value='America'
    ),

    html.Hr(),

    dcc.RadioItems(id='cities-dropdown'),

    html.Hr(),

    html.Div(id='display-selected-values')
])

@app.callback(
    dash.dependencies.Output('cities-dropdown', 'options'),
    [dash.dependencies.Input('countries-dropdown', 'value')])
def set_cities_options(selected_country):
    return [{'label': i, 'value': i} for i in all_options[selected_country]]

@app.callback(
    dash.dependencies.Output('cities-dropdown', 'value'),
    [dash.dependencies.Input('cities-dropdown', 'options')])
def set_cities_value(available_options):
    return None

@app.callback(
    dash.dependencies.Output('display-selected-values', 'children'),
    [dash.dependencies.Input('countries-dropdown', 'value'),
     dash.dependencies.Input('cities-dropdown', 'value')])
def set_display_children(selected_country, selected_city):
    if selected_city is None:
        return ''
    else:
        return u'{} is a city in {}'.format(
            selected_city, selected_country,
        )

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

Oh wow that just works. Now why would I be getting this message when I do that in my app?

You have already assigned a callback to the output
with ID “provinces-list” and property “options”. An output can only have
a single callback function. Try combining your inputs and
callback functions together into one function.

I see that what you are doing differently is to have the input be the component 'options' and the output be the component 'value'. I wouldn’t have thought of that. Is that what is meant by “Try combining your inputs and callback functions together into one function.” Either way, thanks for spelling it out for me!

That error message means that you have 2 (or more) functions that have the same Output. In dash, an output can only have a single callback function, so if you have something like

@app.callback(Output('content', 'children'), [Input('dropdown-1', 'value')])
...

@app.callback(Output('content', 'children'), [Input('dropdown-2', 'value')])
...

then it needs to be re-written like

@app.callback(Output('content', 'children'), [Input('dropdown-1', 'value'), Input('dropdown-2', 'value')])
...

1 Like