From 90fa4c36c51960ace9486438878cd2d6fea38650 Mon Sep 17 00:00:00 2001 From: CNFeffery Date: Tue, 26 Aug 2025 10:58:01 +0800 Subject: [PATCH 1/3] Fix #3366 --- dash/dash-renderer/src/actions/callbacks.ts | 12 ++++- ...multiple_output_return_single_no_update.py | 46 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 tests/integration/clientside/test_clientside_multiple_output_return_single_no_update.py diff --git a/dash/dash-renderer/src/actions/callbacks.ts b/dash/dash-renderer/src/actions/callbacks.ts index 78b2ad1550..8f3c4f97fd 100644 --- a/dash/dash-renderer/src/actions/callbacks.ts +++ b/dash/dash-renderer/src/actions/callbacks.ts @@ -226,8 +226,16 @@ function refErr(errors: any, paths: any) { const getVals = (input: any) => Array.isArray(input) ? pluck('value', input) : input.value; -const zipIfArray = (a: any, b: any) => - Array.isArray(a) ? zip(a, b) : [[a, b]]; +const zipIfArray = (a: any, b: any) => { + if (Array.isArray(a)) { + // For client-side callbacks with multiple Outputs, only return a single dash_clientside.no_update + if (b?.description === 'Return to prevent updating an Output.') { + return zip(a, [b]); + } + return zip(a, b); + } + return [[a, b]]; +}; function cleanOutputProp(property: string) { return property.split('@')[0]; diff --git a/tests/integration/clientside/test_clientside_multiple_output_return_single_no_update.py b/tests/integration/clientside/test_clientside_multiple_output_return_single_no_update.py new file mode 100644 index 0000000000..4ba55a2c40 --- /dev/null +++ b/tests/integration/clientside/test_clientside_multiple_output_return_single_no_update.py @@ -0,0 +1,46 @@ +from dash import ( + Dash, + Input, + Output, + html, + clientside_callback, +) + + +def test_cmorsnu001_clientside_multiple_output_return_single_no_update(dash_duo): + app = Dash(__name__) + app.layout = html.Div( + [ + html.Button("trigger", id="trigger-demo"), + html.Div("demo1", id="output-demo1"), + html.Div("demo2", id="output-demo2"), + ], + style={"padding": 50}, + ) + + clientside_callback( + """(n_clicks) => { + try { + return window.dash_clientside.no_update; + } catch (e) { + return [null, null]; + } + }""", + Output("output-demo1", "children"), + Output("output-demo2", "children"), + Input("trigger-demo", "n_clicks"), + prevent_initial_call=True, + ) + + dash_duo.start_server(app) + + trigger_clicker = dash_duo.wait_for_element("#trigger-demo") + trigger_clicker.click() + dash_duo.wait_for_text_to_equal( + "#output-demo1", + "demo1", + ) + dash_duo.wait_for_text_to_equal( + "#output-demo2", + "demo2", + ) From d339423388771f2889e137bac7ce6770db77ed03 Mon Sep 17 00:00:00 2001 From: CNFeffery Date: Tue, 26 Aug 2025 11:07:50 +0800 Subject: [PATCH 2/3] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9bc7db83b..252898131b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ## Fixed - [#3395](https://github.com/plotly/dash/pull/3395) Fix Components added through set_props() cannot trigger related callback functions. Fix [#3316](https://github.com/plotly/dash/issues/3316) - [#3397](https://github.com/plotly/dash/pull/3397) Add optional callbacks, suppressing callback warning for missing component ids for a single callback. +- [#3415](https://github.com/plotly/dash/pull/3415) Fix the error triggered when only a single no_update is returned for client-side callback functions with multiple Outputs. Fix [#3366](https://github.com/plotly/dash/issues/3366) ## [3.2.0] - 2025-07-31 From ae473a6ad7a0b109b78e094ace777e05d3d234c1 Mon Sep 17 00:00:00 2001 From: CNFeffery Date: Tue, 26 Aug 2025 21:18:47 +0800 Subject: [PATCH 3/3] Update the judgment conditions for no_update --- dash/dash-renderer/src/actions/callbacks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/dash-renderer/src/actions/callbacks.ts b/dash/dash-renderer/src/actions/callbacks.ts index 8f3c4f97fd..ff677fee94 100644 --- a/dash/dash-renderer/src/actions/callbacks.ts +++ b/dash/dash-renderer/src/actions/callbacks.ts @@ -229,7 +229,7 @@ const getVals = (input: any) => const zipIfArray = (a: any, b: any) => { if (Array.isArray(a)) { // For client-side callbacks with multiple Outputs, only return a single dash_clientside.no_update - if (b?.description === 'Return to prevent updating an Output.') { + if (b === (window as any).dash_clientside.no_update) { return zip(a, [b]); } return zip(a, b);