dcc.Dropdown clearable button does not set the value to None

Hey everyone!

I believe I encountered a bug unless I am wrong: I have a dcc.Dropdown with clearable set to True, it is important for the dropdown to be clearable. I have a callback set on the dropdown, when the value of the dropdown changes. This callback is called both on when a dropdown is selected and when it is cleared which is what I need, but I need the callback logic to go differentely based on the fact if the dropdown value was set to some value or if it was cleared.

That is when I ran into a problem - I print debugged the code and print the value in the dropdown to see what is wrong. If the dropdown value is cleared, the callback is fired, but the value for some reason is not None, it is the last value of the dropdown, even if on the UI it looks like None (the default Search word is displayed and nothing is chosen). What is even weirder is the fact that if there is only one value available in the dropdown options which is chosen and then cleared, the callback is called with the same value - the value did NOT change.



image

Am I misunderstanding what is happening or is this a bug?

HI @martin.vlacil ,

there must be something else in your code.

from dash import Input, Output, html, dcc
import dash

app = dash.Dash(__name__,)
app.layout = html.Div(
    [
        dcc.Dropdown(
            id='drop',
            options=[1, 2, 3],
            clearable=True
        ),
        html.Div(id='message'),
    ]
)


@app.callback(
    Output('message', 'children'),
    Input('drop', 'value'),
    prevent_initial_call=True
)
def show_data(data):
    return 'returned value is None' if data is None else 'returned value is not None'


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

I tried your code and it really sets to None, any idea why mine don’t? I cannot figure it out

This is where I define the dropdowns

def generate_dropdowns():
    df = pd.read_csv("./data_source/NKFAM50Hz_Reduced.csv", encoding = "ISO-8859-1")

    df.columns = ["product_number", "product_name", "pump_name", "actual_impeller", "min/max", "impeller", "poles", "flow_value",
    "flow_unit", "head_value", "head_unit", "dnin", "dnout", "weight_value", "weight_unit", "speed_control", "rated_speed_value",
    "rated_speed_unit", "motor_size_value", "motor_size_unit", "rpm_max", "q_min", "q_max", "points_speed_1/min", "q_bep_m3/h",
    "eta2_bep_%", "h_bep_m", "p2_bep_kw", "n_bep_1/min", "rho_times_g", "qs", "hs", "p2s", "npshs", "ns", "q", "h", "p2", "npsh",
    "coefficient_segment", "rpm_min", "head_poly", "power_poly", "npsh_poly", "poly_fit_image"]

    product_names = []
    actual_impellers = [impeller for impeller in df["actual_impeller"].unique().tolist()]
    poles_numbers = [number for number in df["poles"].unique().tolist()]
    speed_controls = [control for control in df["speed_control"].unique().tolist()]

    for product_name in df['product_name'].tolist():
        product_names.append(product_name.split("/")[0])
    
    #getting the unique values
    product_names = list(dict.fromkeys(product_names))
    
    names = ["Product name", "Actual Impeller", "Number of Poles", "Speed Control"]
    options = [
        product_names,
        actual_impellers,
        poles_numbers,
        speed_controls
    ]
    dropdowns = []
    disabled = False

    for name, option_list in zip(names, options):
        dropdowns.append(
            dbc.Col([
                html.Br(),
                dbc.Col(html.Label(name), style = {"textAlign": "start"}),
                dcc.Dropdown(
                    options = option_list,
                    clearable = True,
                    disabled = disabled,
                    id = {"type": "dropdown", "index": name.lower().replace(" ", "-")}
                ),
            ],
                width = 3, lg = {"size": 3}, md = {"size": 4}, sm = {"size": 5},
            ),
        )

    return dropdowns

And this the callback

@app.callback(
    Output({"type": "dropdown", "index": "product-name"}, "options"),
    Output({"type": "dropdown", "index": "actual-impeller"}, "options"),
    Output({"type": "dropdown", "index": "number-of-poles"}, "options"),
    Output({"type": "dropdown", "index": "speed-control"}, "options"),
    Input({"type": "dropdown", "index": "product-name"}, "value"),
    Input({"type": "dropdown", "index": "actual-impeller"}, "value"),
    Input({"type": "dropdown", "index": "number-of-poles"}, "value"),
    Input({"type": "dropdown", "index": "speed-control"}, "value"),
    State("store-dataset", "data"),
    prevent_initial_call = True
)
def update_dropdowns(product_name, actual_impeller, number_of_poles, speed_control, dataset):

    product_options = dash.no_update
    impeller_options = dash.no_update
    poles_options = dash.no_update
    speed_options = dash.no_update

    df = pd.read_csv("./data_source/NKFAM50Hz_Reduced.csv", encoding = "ISO-8859-1")
    df.columns = ["product_number", "product_name", "pump_name", "actual_impeller", "min/max", "impeller", "poles", "flow_value",
    "flow_unit", "head_value", "head_unit", "dnin", "dnout", "weight_value", "weight_unit", "speed_control", "rated_speed_value",
    "rated_speed_unit", "motor_size_value", "motor_size_unit", "rpm_max", "q_min", "q_max", "points_speed_1/min", "q_bep_m3/h",
    "eta2_bep_%", "h_bep_m", "p2_bep_kw", "n_bep_1/min", "rho_times_g", "qs", "hs", "p2s", "npshs", "ns", "q", "h", "p2", "npsh",
    "coefficient_segment", "rpm_min", "head_poly", "power_poly", "npsh_poly", "poly_fit_image"]
    df['poles'] = df['poles'].fillna(0)

    df1 = pd.DataFrame.from_dict(dataset)
    
    #value_when_true if condition else value_when_false
    df1 = df1[df1["product_name"].str.contains(product_name)] if product_name is not None else df1
    df1 = df1[df1["actual_impeller"] == actual_impeller] if actual_impeller is not None else df1
    df1 = df1[df1["poles"] == number_of_poles] if number_of_poles is not None else df1
    df1 = df1[df1["speed_control"].str.contains(speed_control)] if speed_control is not None else df1

    product_options = []
    for product_name in df1['product_name'].tolist():
        product_options.append(product_name.split("/")[0])
    
    #getting the unique values
    product_options = list(dict.fromkeys(product_options))
    impeller_options = [impeller for impeller in df1["actual_impeller"].unique().tolist()]
    poles_options = [number for number in df1["poles"].unique().tolist()]
    speed_options = [speed for speed in df1["speed_control"].unique().tolist()]

    component_id = dash.callback_context.triggered[0]["prop_id"]
    if "product-name" in component_id and product_name is not None:
        print(product_name)
        product_options = dash.no_update
    elif "actual-impeller" in component_id and actual_impeller is not None:
        impeller_options = dash.no_update
    elif "number-of-poles" in component_id and number_of_poles is not None:
        poles_options = dash.no_update
    elif "speed-control" in component_id and speed_control is not None:
        speed_options = dash.no_update

    return product_options, impeller_options, poles_options, speed_options

To clarify, these dropdown filter the data from the Store component, it should be possible to filter by any order so I am trying to make it so on any dropdown value change, the dataset get filtered with that dropdown and the other’s options are updated with the remaining matching options, while not changing the options of the chosen dropdown. It should be possible to clear the dropdown and then the filtering called again.

I think this is the reason. You should use a different variable for looping since you use it as function parameter already

2 Likes

That helped, thank you so much!

1 Like