Skip to content

Commit b5ce179

Browse files
authored
Merge pull request #3768 from AnnMarieW/improve-dropdown-performance
optimize dropdown search
2 parents ce6d0f0 + c0da665 commit b5ce179

2 files changed

Lines changed: 33 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
1919
- [#3738](https://github.com/plotly/dash/pull/3738) Add missing `stacklevel=2` to `warnings.warn()` calls so warnings report the caller's location instead of internal Dash source lines
2020
- [#3740](https://github.com/plotly/dash/pull/3740) Fix cannot tab into dropdowns in Safari
2121
- [#2462](https://github.com/plotly/dash/issues/2462) Allow `MATCH` in `Input`/`State` when the callback's `Output` has no wildcards (fixed-id Output, no Output, or `ALL`-only wildcard Output). `ALLSMALLER` still requires a corresponding `MATCH` in an Output.
22+
- [#3768](https://github.com/plotly/dash/pull/3768) Improved `Dropdown` search performance for large options lists
2223
- [#3759](https://github.com/plotly/dash/pull/3759) Fix the issue where `Patch` objects cannot be updated via `set_props()` in `websocket` callback. Fix [#3742](https://github.com/plotly/dash/issues/3742)
2324
- [#3789](https://github.com/plotly/dash/pull/3789) Fixed extra wrapper in `DatePickerRange` and `DatePickerSingle` causing styling and layout issues.
2425

components/dash-core-components/src/utils/dropdownSearch.ts

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface SanitizedOptions {
2323
options: DetailedOption[];
2424
indexes: string[];
2525
valueSet: Set<OptionValue>;
26+
search: Search;
2627
}
2728

2829
// Single-pass sanitization via sanitizeOptions, plus detection of
@@ -55,38 +56,48 @@ export function sanitizeDropdownOptions(
5556
indexes.push('search');
5657
}
5758

58-
return {options: sanitized, indexes, valueSet};
59-
}
60-
61-
export function filterOptions(
62-
options: SanitizedOptions,
63-
searchValue?: string,
64-
search_order?: 'index' | 'original'
65-
): DetailedOption[] {
66-
if (!searchValue) {
67-
return options.options;
68-
}
69-
59+
// Build the search index ONCE during sanitization
7060
const search = new Search('value');
7161
search.searchIndex = new UnorderedSearchIndex();
7262
search.indexStrategy = new AllSubstringsIndexStrategy();
7363
search.tokenizer = TOKENIZER;
7464

75-
options.indexes.forEach(index => {
65+
indexes.forEach(index => {
7666
search.addIndex(index);
7767
});
7868

79-
if (options.options.length > 0) {
80-
search.addDocuments(options.options);
69+
if (sanitized.length > 0) {
70+
search.addDocuments(sanitized);
71+
}
72+
73+
return {
74+
options: sanitized,
75+
indexes,
76+
valueSet,
77+
search,
78+
};
79+
}
80+
81+
export function filterOptions(
82+
options: SanitizedOptions,
83+
searchValue?: DropdownProps['search_value'],
84+
searchOrder?: DropdownProps['search_order']
85+
): DetailedOption[] {
86+
if (!searchValue) {
87+
return options.options;
8188
}
8289

83-
const searchResults =
84-
(search.search(searchValue) as DetailedOption[]) || [];
90+
const results =
91+
(options.search.search(searchValue) as DetailedOption[]) || [];
8592

86-
if (search_order === 'original') {
87-
const resultSet = new Set(searchResults);
88-
return options.options.filter(option => resultSet.has(option));
93+
// Preserve original option order
94+
if (searchOrder === 'original') {
95+
const resultSet = new Set(results.map(option => option.value));
96+
97+
return options.options.filter(option =>
98+
resultSet.has(option.value)
99+
);
89100
}
90101

91-
return searchResults;
102+
return results;
92103
}

0 commit comments

Comments
 (0)