Allow User to Create New Options in `dcc.Dropdown`

I was wondering if there is a way, to allow users to create new options while selecting from a (potentially empty) dcc.Dropdown.

The main idea is for this to also function as a data entry tool. The great autocomplete / search functionality available in dcc.Dropdown would make it easy for users to keep track of what they entered.

To be clear, this idea is basically Shiny’s selectizeInput function with the option of create=TRUE:
https://shiny.rstudio.com/articles/selectize.html

selectizeInput(

‘foo’, label = NULL, choices = state.name,
options = list(create = TRUE)
)

The user starts entering data, and gets suggestions.
They can select one of the suggestions, or hit Enter to create a new value.

Thanks!

Yep! You can create a callback that targets the options property of the dropdown component. The trick is also including the options as a State of the callback so we can append the new value to the current values (you can’t add it as an Input as this will result in a cyclic dependency)

@app.callback(Output('dropdown', 'options'), [Input('input', 'value')], [State('dropdown', 'options')])
def callback(new_value, current_options):
    if not new_value:
        return current_options

    current_options.append({'label': new_value, 'value': new_value})
    return current_options

This is not quite as streamlined as the Shiny behaviour you described though, as this is not built into the dropwdown component, just using Dash itself to update the options. So you’ll need to have an Input component (here with ID ‘input’) for users to put their new value in. Also it just occurs to me know, that you probably don’t want that being triggered by an Input dependency, as you’ll get a new option for every character someone types. Maybe a click event on a button could be safer:

app.layout = html.Div([
    dcc.Dropdown(
        id='dropdown',
        options=[
            {'label': 'a', 'value': 'a'},
            {'label': 'b', 'value': 'b'},
            {'label': 'c', 'value': 'c'},
        ],
        value='a'
    ),
    dcc.Input(id='input', value=''),
    html.Button('Add Option', id='submit'),
])

@app.callback(
    Output('dropdown', 'options'),
    [],
    [State('input', 'value'), State('dropdown', 'options')],
    [Event('submit', 'click')]
)
def callback(new_value, current_options):
    if not new_value:
        return current_options

    current_options.append({'label': new_value, 'value': new_value})
    return current_options
2 Likes

Thanks for the immediate response!

Just ran the code and it seems to work. I can simply print out the entered values, so the user can keep track of what was entered.

You’re right about cyclic dependency. I tried to modify this recipe, https://github.com/plotly/dash-recipes/blob/master/dash-append-element-dropdown-options.py and every letter I typed ended up in the list of options.

I wasn’t very familiar with Event either.

Thanks again!

1 Like

Would you happen to have any recommendations for an updated solution, now that the Event module has been removed?

Change Event to Input and it will work the same.

@app.callback(
    Output('dropdown', 'options'),
    [Input('submit', 'click')]
    [State('input', 'value'),
     State('dropdown', 'options')],
)
def callback(n_clicks,new_value,current_options):
    if not n_clicks:
        return no_update
    ...

Came across the need of doing this today. I wasn’t happy with having a separate input field so here is my take using the dropdown menu only.

As the user types in the search field of the dropdown menu, a new option is added / removed on each key strokes, depending if new key stroke is added or removed so that the dropdown menu doesn’t end up with a lot of unwanted options and just the final one the user entered. The user can add an unlimited number of options as one will be created on each “select” or “Enter”.

@app.callback(
    Output('demo-dropdown', 'options'),
    Output('demo-dropdown-search_value', 'data'),
    Input('demo-dropdown', 'search_value'),
    State('demo-dropdown', 'options'),
    State('demo-dropdown-search_value', 'data'),
    prevent_initial_call=True
)
def make_new_dropdown_entry(stroke, options, previous_stroke):
    # PS: this won't work for new option of length 1
    # PS 2: a new option will be set if the user deletes more than one stroke at a time
    n = len(stroke)
    try:
        n_previous = len(previous_stroke)
    except:
        pass
        
    if previous_stroke is None and n > 0:
        # first stroke since on page, add option
        options += [{'label': stroke, 'value': stroke}]
    elif n > n_previous and n_previous > 0:
        # subsequent strokes, increasing, remove last invalid option and add latest option
        options = options[:-1] + [{'label': stroke, 'value': stroke}]
    elif n < n_previous and n_previous > 0:
        # subsequent strokes, decreasing
        if n > 0:
            # stroke length still valid, remove last invalid option and add latest option
            options = options[:-1] + [{'label': stroke, 'value': stroke}]
        else:
            if n < n_previous - 1:
                # on validation, one last callback with search_value of len 0 is triggered,
                # keep latest option as it is likely the one the user wants
                stroke = None  # reset to default
            else:
                # user deleted all inputs, remove latest option
                options = options[:-1]

    return options, stroke

where demo-dropdown is the dropdown menu and demo-dropdown-search_value is a dcc.Store that keeps track of the stroke from the search field.