Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

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

Closed
emilykl opened this issue Dec 16, 2021 · 6 comments · Fixed by #1970

Comments

@emilykl
Copy link
Contributor

emilykl commented Dec 16, 2021

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]
@emilykl emilykl changed the title [BUG] dcc.Dropdown value does not update when selected option removed from options -- but UI does update [BUG] dcc.Dropdown value does not update when selected option removed from options -- but UI does update Dec 16, 2021
@emilykl
Copy link
Contributor Author

emilykl commented Dec 16, 2021

@alexcjohnson It seems to me that this could be fixed by adding a few lines of code here to filter value and update it with setProps but not sure if that would have unintended side effects -- any thoughts?

@alexcjohnson
Copy link
Collaborator

Nice debugging! I don't think anyone will be bothered by this triggering a value update, as long as this only happens when value actually needs to change.

Would be great if we could use this opportunity to remove the UNSAFE_ method - it looks to me as though the value update belongs in componentDidUpdate, and we should get rid of state.filterOptions entirely, just memoize a flavor of createFilterOptions as a simple function of options (perhaps https://ramdajs.com/docs/#memoizeWith) and call it directly in render - ie instead of

const {filterOptions} = this.state;

make it so we can call:

const filterOptions = memoizedFilterOptions(options);

@alexcjohnson
Copy link
Collaborator

ramda.memoizeWith comes with a potential performance problem though: if options is large and changes frequently we'll end up holding all previous values in memory. Might be better to just include a simple memoizeOne like we do in the table here

@alexcjohnson
Copy link
Collaborator

one more nuance though - if we do add a memoizeOne it will need to be invoked in the component constructor, so that every Dropdown gets its own single-item cache, rather than one global single-item cache, otherwise the cache will fail as soon as there are two dropdowns on the page invalidating each other.

@baharmahmudlu
Copy link

@alexcjohnson I have the same issue with dash 2.8.1 version, is it suppose to be solved with version? how i can solve this bug?

@Thuener
Copy link

Thuener commented Apr 21, 2023

I was running the example from @emilykl on version 2.9.3 and having the same issue as before. Maybe some regression?

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants