Skip to content

Commit

Permalink
Merge pull request #2548 from plotly/fix/cap-side-effect
Browse files Browse the repository at this point in the history
Fix component as props callback triggering other callbacks not in response.
  • Loading branch information
T4rk1n authored May 30, 2023
2 parents fa486be + 1612f5a commit cbf9591
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
## Fixed

- [#2545](https://github.com/plotly/dash/pull/2545) Fix typescript objectOf generation.
- [#2548](https://github.com/plotly/dash/pull/2548) Fix component as props callback triggering other callbacks not in response, fix [#2487](https://github.com/plotly/dash/issues/2487).

## [2.10.0] - 2023-05-25

Expand Down
15 changes: 6 additions & 9 deletions dash/dash-renderer/src/actions/dependencies_ts.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import {
all,
any,
assoc,
concat,
difference,
filter,
flatten,
includes,
isEmpty,
keys,
map,
Expand Down Expand Up @@ -312,13 +310,12 @@ export const getLayoutCallbacks = (
rootId = stringifyId(rootId);
// Filter inputs that are not present in the response
callbacks = callbacks.filter(cb =>
any(
(inp: any) =>
!(
stringifyId(inp.id) === rootId &&
!includes(inp.property, options.filterRoot)
),
cb.callback.inputs
cb.callback.inputs.reduce(
(previous: any, input: any) =>
previous ||
(stringifyId(input.id) == rootId &&
options.filterRoot.includes(input.property)),
false
)
);
}
Expand Down
64 changes: 63 additions & 1 deletion tests/integration/renderer/test_component_as_prop.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from dash_test_components import ComponentAsProp

from dash.dcc import Checklist
from dash.dcc import Checklist, Dropdown
from dash.html import Button, Div, Span


Expand Down Expand Up @@ -379,3 +379,65 @@ def opts(n):

dash_duo.find_elements("#b label > input")[0].click()
dash_duo.wait_for_text_to_equal("#counter", "1")


def test_rdcap004_side_effect_same_component(dash_duo):
options = [
{"label": "aa1", "value": "aa1"},
{"label": "aa2", "value": "aa2"},
{"label": "aa3", "value": "aa3"},
{"label": "best value", "value": "bb1"},
{"label": "better value", "value": "bb2"},
{"label": "bye", "value": "bb3"},
]

app = Dash(__name__)

app.layout = Div(
[
Div(
["Single dynamic Dropdown", Dropdown(id="my-dynamic-dropdown")],
style={"width": 200, "marginLeft": 20, "marginTop": 20},
),
Button(
"Reset",
id="button",
n_clicks=0,
),
Div(0, id="counter"),
]
)
app.clientside_callback(
"function(_, prev) {return parseInt(prev) + 1}",
Output("counter", "children"),
Input("my-dynamic-dropdown", "value"),
State("counter", "children"),
prevent_initial_call=True,
)

@app.callback(
Output("my-dynamic-dropdown", "options"),
Input("my-dynamic-dropdown", "search_value"),
)
def update_options(search_value):
if search_value is None:
return options
return [o for o in options if search_value in o["label"]]

@app.callback(
Output("my-dynamic-dropdown", "value"),
Input("button", "n_clicks"),
)
def on_button(n_clicks):
return None

dash_duo.start_server(app)

# Initial callback
dash_duo.wait_for_text_to_equal("#counter", "1")

search = dash_duo.wait_for_element("#my-dynamic-dropdown input")

search.send_keys("a")

dash_duo.wait_for_text_to_equal("#counter", "1")

0 comments on commit cbf9591

Please sign in to comment.