Skip to content

[BUG] dcc.Dropdown value does not update when selected option removed from options -- but UI does update #1868

@emilykl

Description

@emilykl

Describe your context

  • replace the result of pip list | grep dash below
dash               2.0.0
dash-renderer      1.9.1
  • if frontend related, tell us your Browser, Version and OS

Bug is not browser-specific but these are my stats:

- OS: Mac OS 11.2.2 (Big Sur)
- Browser: Safari
- Version: 14.0.3

Describe the bug

When the options of a dcc.Dropdown are updated to remove a currently selected option, the UI updates to remove that value, but the value parameter does not update.

This results in unexpected app behaviour because the value of the dropdown is different from what's displayed in the UI.

This behaviour happens with both single-select and multi-select dropdowns.

Expected behavior

Removing an option from options also removes that option from value. Value in UI and code remain in sync.

Screenshots

dropdown-bug.mov

Minimal example

import dash
from dash import dcc, html
from dash.dependencies import Input, Output, State

app = dash.Dash(__name__)
server = app.server  # expose server variable for Procfile

# Set display title
app_title = "Dropdown bug"
app.title = app_title

seats = ["11A", "11B", "11C", "12A", "12B", "15E", "15F"]


def layout():
    return html.Div(
        [
            html.Div(
                [
                    html.P("Available seats: "),
                    dcc.Dropdown(
                        id="available-seats",
                        multi=True,
                        options=[{"label": i, "value": i} for i in seats],
                        value=seats,
                    ),
                ],
            ),
            html.Div(
                [
                    html.P("Chosen seats: "),
                    dcc.Dropdown(id="chosen-seats", multi=True,),
                ],
            ),
            html.Button(id="submit-btn", children="Submit seat choice"),
            html.Div(id="output"),
        ],
    )


app.layout = layout()


@app.callback(
    Output("chosen-seats", "options"), Input("available-seats", "value"),
)
def update_options(available_seats):
    if available_seats is None:
        return []
    else:
        return [{"label": i, "value": i} for i in available_seats]

@app.callback(
    Output("output", "children"),
    Input("submit-btn", "n_clicks"),
    State("chosen-seats", "options"),
    State("chosen-seats", "value"),
    prevent_initial_call=True,
)
def print_value(n_clicks, options, value):
    return [
        html.P(f"Chosen seats are: {value}"),
        html.P(f"Available seats are: {[i['value'] for i in options]}."),
    ]


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

Adding this callback results in what I think is the expected behaviour:

@app.callback(
    Output("chosen-seats", "value"),
    Input("chosen-seats", "options"),
    State("chosen-seats", "value"),
)
def update_value_when_options_change(options, cur_value):
    if cur_value is None:
        return None
    else:
        option_values = {i["value"] for i in options}
        return [i for i in cur_value if i in option_values]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions