DASH chained callback (update checklist based on user input)

I have 2 checklists, country and district. I want the district checklist to update based on the user’s input in country (e.g. if the user selects United Kingdom, I want the district checklist to only display the districts in the United Kingdom).

I tried using the plotly documentation (section " Dash App With Chained Callbacks") but I am getting an error at the end. I get a TypeError that lists are unhashable. Can someone help me with that please?

all_options = {
    'United Kingdom': ['East', 'London', 'South-East', 'South-West', 'North-East', 'North-West', 'Midlands', 'Yorkshire', 'Scotland'],
    'Netherlands': ['Amsterdam']
}

dcc.Checklist(id='country_checklist',
                                  options=[{'label': k, 'value': k} for k in all_options.keys()],
                                  value=[],
                                  labelStyle={'display': 'block'},
                                  inputStyle={"margin-right": "5px"}
                                  ),

dcc.Checklist(className='checkbox',
                                  id='district_checklist',
                                  value=[],
                                  labelStyle={'display': 'block'},
                                  inputStyle={"margin-right": "5px"}
                                  )

@app.callback(
    Output('district_checklist', 'options'),
    Input('country_checklist', 'value'))

def set_district_options(selected_country):
    return [{'label': i, 'value': i} for i in all_options[selected_country]]

Hi @GabrielBKaram

The problem you are facing is related with the component you are using, as the first component is a dcc.Checklist the user can select more than one option, then the value property could have one value or more, thus the input you are receiving in the callback is a list and not a unique element, see that if you print that value you will have the value in brackets like [‘United Kingdom’].

To solve this you can change the code to get the first value of the list like this:

    return [{'label': i, 'value': i} for i in all_options[selected_country[0]]]

The code will run, but you will have a problem when the user selects other Country because the checklist will accept more than one selection, in this case the second dcc.Checklist will have all the district of both Countries, because of that, I recomend to use dcc.RadioItems instead of dcc.Checklist.
And because the RadioItems value only have one element, the code to return the options do not need to be changed :grinning:
Also I recomend to add a no_update return to avoid any problem when there is no option selected.
Here is the entire code:

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


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

all_options = {
    'United Kingdom': ['East', 'London', 'South-East', 'South-West', 'North-East', 'North-West', 'Midlands', 'Yorkshire', 'Scotland'],
    'Netherlands': ['Amsterdam']
}

app.layout = html.Div([
    dcc.RadioItems(id='country_checklist',
                                  options=[{'label': k, 'value': k} for k in all_options.keys()],
                                  labelStyle={'display': 'block'},
                                  inputStyle={"margin-right": "5px"}
                                  ),
    dcc.Checklist(className='checkbox',
                                  id='district_checklist',
                                  value=[],
                                  labelStyle={'display': 'block'},
                                  inputStyle={"margin-right": "5px"}
                                  )
])

@app.callback(
    Output('district_checklist', 'options'),
    Input('country_checklist', 'value'))

def set_district_options(selected_country):
    print(selected_country)
    if not selected_country:
        return no_update
    else:
        print(all_options)
        return [{'label': i, 'value': i} for i in all_options[selected_country]]


if __name__ == "__main__":
    app.run_server(debug=True)
2 Likes

cheers, thanks a lot Eduardo! you saved me twice today :smiley:

1 Like