Plotly please fix this Dynamic Options Dropdown bug!

Hello Plotly,

the default behavior of Dash dropdown menu searching includes middle-matches which is heinous, to me at least. My first productive interaction on this board was @chriddyp pointing out the Dynamic Options example at dcc.Dropdown | Dash for Python Documentation | Plotly and I went on to write my own front-matching routine and all was fine. However I have discovered another problem that I initially reported in Possible bug in dcc.Dropdown and want to provide a clear code example and get it fixed.

I and many others like @Eduardo are using Dash to create stock quote graphs. Assume that I have defined a NAMES dict defining the company name for each symbol as shown in the example below. I have three scenarios.

  1. I want to show the data for one stock. I use a dcc.Dropdown with options defined as return [{‘label’: f’{sym} - {NAMES[sym]}’, ‘value’: sym} for sym in syms] I can write a Dynamic Option and AAPL is returned when the user types Apple, ORCL is returned when the user types Oracle, and TSLA is returned when the user types Tesla. Matching the symbol or the name works fine.

  2. I want to show the data for multiple stocks. I need to fit as many symbols as possible in the dropdown so we include only the symbol and not ‘sym - name’ in the label. I use a dcc.Dropdown with multi=True and options defined as [{‘label’: sym, ‘value’: sym} for sym in syms] and everything works great however users must type in the correct symbol name.

  3. Users want to use the multiple stock dropdown, but still type in the company name, and get the correct ticker symbol. As long as I have the NAMES dict to reference and return Dynamic Options correctly, this should be a snap. However running the code below shows that it does not work correctly.

Type in AAPL (or AA). AAPL comes up and is selected properly
Type in Te. TSLA matches properly and is returned in the options with previously selected AAPL, but does not appear in the dropdown. Somehow Dash is still doing a layer of filtering on its own, even though the code shows that the value being returned is [{‘label’: ‘AAPL’, ‘value’: ‘AAPL’}, {‘label’: ‘TSLA’, ‘value’: ‘TSLA’}]

Screen Shot 2021-03-04 at 9.36.59 AM

I tried another test wondering if using options as [{‘label’: sym, ‘value’: f’{sym} {NAMES[sym]}’} for sym in syms] would not filter but it still did.

Plotly please fix this as soon as possible or point out any other method where I can return Dynamic Options that return correctly without matching the label values.

import re

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from dash.exceptions import PreventUpdate


def ddm_opts(syms):
    return [{'label': sym, 'value': sym} for sym in syms]


NAMES = {
    'AAPL': 'Apple Inc',
    'AMZN': 'Amazon Com Inc',
    'ASML': 'Asml Holding Nv',
    'BABA': 'Alibaba Group Holding Ltd',
    'BAC': 'Bank Of America Corp',
    'BRK.A': 'Berkshire Hathaway Inc',
    'CMCSA': 'Comcast Corp',
    'CRM': 'Salesforcecom Inc',
    'CVX': 'Chevron Corp',
    'DE': 'Deere & Co',
    'DHR': 'Danaher Corp',
    'DIS': 'Walt Disney Co',
    'EL': 'Estee Lauder Companies Inc',
    'ENB': 'Enbridge Inc',
    'EQNR': 'Equinor Asa',
    'FB': 'Facebook Inc',
    'FIS': 'Fidelity National Information Services Inc',
    'FISV': 'Fiserv Inc',
    'GE': 'General Electric Co',
    'GOOG': 'Alphabet Inc',
    'GS': 'Goldman Sachs Group Inc',
    'HD': 'Home Depot Inc',
    'HDB': 'Hdfc Bank Ltd',
    'HON': 'Honeywell International Inc',
    'IBM': 'International Business Machines Corp',
    'INTC': 'Intel Corp',
    'INTU': 'Intuit Inc',
    'JD': 'JDcom Inc',
    'JNJ': 'Johnson & Johnson',
    'JPM': 'Jpmorgan Chase & Co',
    'KHC': 'Kraft Heinz Co',
    'KLAC': 'Kla Corp',
    'KO': 'Coca Cola Co',
    'LIN': 'Linde Plc',
    'LLY': 'ELI LILLY & Co',
    'LOW': 'Lowes Companies Inc',
    'MA': 'Mastercard Inc',
    'MRK': 'Merck & Co Inc',
    'MSFT': 'Microsoft Corp',
    'NFLX': 'Netflix Inc',
    'NKE': 'NIKE Inc',
    'NVDA': 'Nvidia Corp',
    'OKTA': 'Okta Inc',
    'ORCL': 'Oracle Corp',
    'ORLY': 'O Reilly Automotive Inc',
    'PDD': 'Pinduoduo Inc',
    'PG': 'PROCTER & GAMBLE Co',
    'PYPL': 'PayPal Holdings Inc',
    'QCOM': 'Qualcomm Inc',
    'QRVO': 'Qorvo Inc',
    'QS': 'QuantumScape Corp',
    'RDS.A': 'Royal Dutch Shell plc',
    'RIO': 'Rio Tinto Ltd',
    'RY': 'Royal Bank Of Canada',
    'SAP': 'Sap Se',
    'SHOP': 'Shopify Inc',
    'SNE': 'Sony Corp',
    'TM': 'Toyota Motor Corp',
    'TSLA': 'Tesla Inc',
    'TSM': 'Taiwan Semiconductor Manufacturing Co Ltd',
    'UNH': 'Unitedhealth Group Inc',
    'UNP': 'Union Pacific Corp',
    'UPS': 'United Parcel Service Inc',
    'V': 'Visa Inc',
    'VALE': 'Vale SA',
    'VZ': 'Verizon Communications Inc',
    'WBK': 'Westpac Banking Corp',
    'WFC': 'Wells Fargo & Company',
    'WMT': 'Walmart Inc',
    'XEL': 'Xcel Energy Inc',
    'XLNX': 'Xilinx Inc',
    'XOM': 'Exxon Mobil Corp',
    'YNDX': 'Yandex NV',
    'YUM': 'Yum Brands Inc',
    'YUMC': 'Yum China Holdings Inc',
    'ZG': 'Zillow Group Inc',
    'ZM': 'Zoom Video Communications Inc',
    'ZTS': 'Zoetis Inc'
}
SYMS = sorted(NAMES.keys())
DDM_OPTS = ddm_opts(SYMS)

app = dash.Dash(__name__)


app.layout = html.Div(
    dcc.Dropdown(id=f'ddm',
                 multi=True,
                 placeholder='Select a Common Stock',
                 style={'width': 1000})
)


@app.callback(
    Output("ddm", "options"),
    [Input("ddm", "search_value"),
     Input("ddm", "value")],
)
def front_loaded_ddm_sym_or_name_matches(search, values):
    if not search:
        raise PreventUpdate
    if values is None:
        values = []

    search = re.sub(r'\\+', '', search)
    # front symbol matches
    fsm = [o for o in DDM_OPTS if re.match(search.upper(), o["label"])]

    # front name matches, using NAMES dict instead of OPTS
    fnm = [o for o in SYMS if re.search(f'^{search}', NAMES[o], re.IGNORECASE) and o not in fsm]
    print(fnm)

    ropts = ddm_opts(values) + fsm + ddm_opts(fnm)
    print(ropts)
    return ropts


if __name__ == "__main__":
    app.run_server(port=8888)

Plotly team, it’s popular problem for any search bars.
Can you, please, fix it or provide a solution how to make a custom one with multi-input?