We use the dcc.Dropdown component like a search bar in our app, with multi enabled.
The search_val that a user types is taken as input to a callback that filters the ‘options’ available to the Dropdown component.
i.e. if we have a list of countries, typing 'United" would filter out countries that don’t include ‘United’ in their name, so the ‘options’ in the dropdown might include “United States, United Kingdom, United Arab Emirates…”.
One downside is that the callback that filters the possible options fires for every character entered into the Dropdown field, so in a deployed app there are 6 callbacks fired by typing “United” that filter the options each time.
I’m wondering if anyone has any ideas of how I could pause the callback request until the user has stopped typing, so that in the previous example the user could type “United” as a whole before the callback filtering the options for the Dropdown is called, and when it is called the input of the search_val is “United”
Any help with this would be appreciated- this problem is slowing down my application for all users whenever there’s a dropdown search. (The callbacks are very fast themselves but the overhead of filtering the options takes long enough for each character that it’s noticeable). Thanks!
That sounds like exactly what I’m looking for but it isn’t in the docs for the component, and it doesn’t work in practice.
TypeError: The `dcc.Dropdown` component (version 2.7.0) with the ID "<my_id>" received an unexpected keyword argument: `debounce`
Allowed arguments: className, clearable, disabled, id, loading_state, maxHeight, multi, optionHeight, options, persisted_props, persistence, persistence_type, placeholder, search_value, searchable, style, value
from dash import Dash, dcc, html, Input, Output, State, callback, no_update
app = Dash(__name__)
city_selections = [
"New York City",
"Montreal",
"San Francisco",
"Denver",
"Jackson Hole",
"Seattle",
]
app.layout = html.Div(
[
dcc.Dropdown(
id="dropdown",
options=["Montreal", "San Francisco"],
value=["Montreal", "San Francisco"],
multi=True,
)
]
)
@callback(
Output("dropdown", "options"),
Input("dropdown", "search_value"),
State("dropdown", "value"),
)
def options_one(user_input, selections):
"""
Callback fires every time the 'search_value' changes
in the dropdown menu, which is whenever the user adds
or deletes a character from the search field.
"""
# nothing currently selected in the searchbar
if not selections:
selections = []
# user has deleted any typed search term
if not user_input:
return no_update
# patter match strings in selectable options against the user's input
opts = [city for city in city_selections if user_input.lower() in city.lower()]
print(f"User input: {user_input}")
print(f"Possible options: {opts}")
"""
Normally, the length of the 'city_selections' value in our app's context
is around 15,000 entries long, which is too long for the searchbar,
so we return a trimmed options list like this, limiting the number of options
to a manageable 250 + selections values:
if(len(opts) > 250):
return opts[:250] + selections
else:
return opts + selections
"""
# concat previous selections onto options, otherwise they won't
# be kept as selected values.
return opts + selections
if __name__ == "__main__":
app.run_server(debug=True)
This MRE implements the way we handle option filtering in our app.
What would be nice is if the parameter ‘debounce’ was available like @jinnyzor mentioned
so that the callback would only fire when the user stopped typing for a reasonable amount of time.
Okay I’ll give that a try! Thanks for the suggestion. That’s a good start- just knowing that ‘debounce’ is probably the right term to look for is useful.
Interesting findings: with the following code the debounce property doesn’t seem to do anything:
from dash import Dash, dcc, html, Input, Output, State, callback, no_update
import dash_mantine_components as dmc
app = Dash(__name__)
city_selections = [
"New York City",
"Montreal",
"San Francisco",
"Denver",
"Jackson Hole",
"Seattle",
]
app.layout = html.Div(
[
dmc.MultiSelect(
label="Select a City:",
id="dropdown",
data=["Montreal", "San Francisco"],
value=["Montreal", "San Francisco"],
searchable=True,
clearable=True,
debounce=50000,
)
]
)
@callback(
Output("dropdown", "data"),
Input("dropdown", "searchValue"),
State("dropdown", "value"),
)
def options_one(user_input, selections):
"""
Callback fires every time the 'search_value' changes
in the dropdown menu, which is whenever the user adds
or deletes a character from the search field.
"""
# nothing currently selected in the searchbar
if not selections:
selections = []
# user has deleted any typed search term
if not user_input:
return no_update
# patter match strings in selectable options against the user's input
opts = [city for city in city_selections if user_input.lower() in city.lower()]
print(f"User input: {user_input}")
print(f"Possible options: {opts}")
"""
Normally, the length of the 'city_selections' value in our app's context
is around 15,000 entries long, which is too long for the searchbar,
so we return a trimmed options list like this, limiting the number of options
to a manageable 250 + selections values:
if(len(opts) > 250):
return opts[:250] + selections
else:
return opts + selections
"""
# concat previous selections onto options, otherwise they won't
# be kept as selected values.
return opts + selections
if __name__ == "__main__":
app.run_server(debug=True)
If I let the component handle its own search (just not registering a callback to the ‘searchValue’ field of the component) the debounce doesn’t seem to apply to the user’s input either- the filtered results are returned immediately. Maybe it’s not implemented? There aren’t any indications that it actively applies to the MultiSelect component in the codebase: