Skip to content

Commit e15ef05

Browse files
authored
Merge branch 'dev' into fix/ws-connection-management
2 parents c07643c + 715dae8 commit e15ef05

7 files changed

Lines changed: 101 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
2323
- [#3768](https://github.com/plotly/dash/pull/3768) Improved `Dropdown` search performance for large options lists
2424
- [#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)
2525
- [#3789](https://github.com/plotly/dash/pull/3789) Fixed extra wrapper in `DatePickerRange` and `DatePickerSingle` causing styling and layout issues.
26+
- [#3799](https://github.com/plotly/dash/pull/3799) Fixed dropdown crash when filtering large datasets with component-based labels by using stable item keys for virtualized rows.
2627

2728
## [4.2.0rc3] - 2026-05-12
2829

components/dash-core-components/src/utils/optionRendering.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,9 @@ export const OptionsList = forwardRef<OptionsListHandle, OptionsListProps>(
400400
className="dash-options-list-virtualized"
401401
onItemsRendered={handleItemsRendered}
402402
itemData={itemData}
403+
itemKey={(index, data) =>
404+
String(data.options[index]?.value ?? index)
405+
}
403406
>
404407
{Row}
405408
</VariableSizeList>

components/dash-core-components/tests/integration/dropdown/test_search_value.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,41 @@ def send_key(key):
107107
last_option.click()
108108

109109
dash_duo.wait_for_text_to_equal("#output", "value=opt_100")
110+
111+
112+
def test_ddsv003_dropdown_virtualized_component_label_filtering(dash_duo):
113+
app = Dash(__name__)
114+
115+
options = [
116+
{
117+
"label": html.Div(["Item ", html.Span(f"#{i}")]),
118+
"value": f"item_{i}",
119+
"search": f"item_{i}",
120+
}
121+
for i in range(1, 2000)
122+
]
123+
124+
app.layout = html.Div(
125+
[
126+
dcc.Dropdown(
127+
id="dd",
128+
options=options,
129+
value="item_1",
130+
searchable=True,
131+
clearable=True,
132+
)
133+
]
134+
)
135+
136+
dash_duo.start_server(app)
137+
138+
dropdown = dash_duo.find_element("#dd")
139+
dropdown.click()
140+
141+
search = dash_duo.find_element(".dash-dropdown-search")
142+
143+
# trigger filtering path that used to crash virtualized list
144+
search.send_keys("199")
145+
sleep(0.5)
146+
147+
assert dash_duo.get_logs() == []

dash/backends/_quart.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ def create_app(
122122
def register_assets_blueprint(
123123
self, blueprint_name: str, assets_url_path: str, assets_folder: str # type: ignore[name-defined]
124124
):
125-
126125
bp = Blueprint(
127126
blueprint_name,
128127
__name__,

dash/dash.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@
135135
_ID_STORE = "_pages_store"
136136
_ID_DUMMY = "_pages_dummy"
137137

138+
_UNINITIALIZED = object() # Sentinel for tracking init_app state
139+
138140
DASH_VERSION_URL = "https://dash-version.plotly.com:8080/current_version"
139141

140142
# Handles the case in a newly cloned environment where the components are not yet generated.
@@ -731,6 +733,17 @@ def init_app(self, app: Optional[Any] = None, **kwargs) -> None:
731733
)
732734
if app is not None:
733735
self.server = app
736+
# Also update the backend's server reference so routes are registered
737+
# on the correct server (important when using server=False pattern)
738+
self.backend.server = app
739+
740+
# Skip registration if already initialized on this server
741+
# This prevents double registration when init_app() is called multiple times
742+
# (e.g., with flask run pattern where __init__ calls init_app, then user does too)
743+
if getattr(self, "_initialized_server", _UNINITIALIZED) is self.server:
744+
return
745+
self._initialized_server = self.server
746+
734747
bp_prefix = config.routes_pathname_prefix.replace("/", "_").replace(".", "_")
735748
assets_blueprint_name = f"{bp_prefix}dash_assets"
736749
self.backend.register_assets_blueprint(

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/unit/test_configs.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,3 +524,43 @@ def test_csrf_config_read_only():
524524
app.config.csrf_token_name = "something_else"
525525
with pytest.raises(AttributeError):
526526
app.config.csrf_header_name = "something_else"
527+
528+
529+
def test_init_app_with_flask_run_pattern():
530+
"""Test that init_app works correctly with the flask run pattern.
531+
532+
This tests the fix for https://github.com/plotly/dash/issues/3787
533+
where using flask run would cause a ValueError about duplicate blueprint
534+
registration because init_app was called twice (once automatically in
535+
__init__ with server=True, and once by the user in their create_app factory).
536+
"""
537+
# Simulate the flask run pattern where:
538+
# 1. Dash app is created with server=True (default)
539+
# 2. User's create_app factory calls init_app with their Flask server
540+
external_server = Flask("external_test")
541+
app = Dash(__name__)
542+
543+
# This should NOT raise "ValueError: The name '_dash_assets' is already registered"
544+
app.init_app(external_server)
545+
546+
# Verify the backend now uses the external server
547+
assert app.server is external_server
548+
assert app.backend.server is external_server
549+
550+
551+
def test_init_app_server_false_pattern():
552+
"""Test that init_app works correctly when server=False is used.
553+
554+
This tests the fix for https://github.com/plotly/dash/issues/3787
555+
where using server=False and then calling init_app would result in
556+
404 errors because the backend's server reference was not updated.
557+
"""
558+
external_server = Flask("external_test_false")
559+
app = Dash(__name__, server=False)
560+
561+
# Call init_app with the external server
562+
app.init_app(external_server)
563+
564+
# Verify both the app and backend use the external server
565+
assert app.server is external_server
566+
assert app.backend.server is external_server

0 commit comments

Comments
 (0)