Skip to content

Commit

Permalink
Merge pull request #2429 from plotly/fix-2411
Browse files Browse the repository at this point in the history
Fix side effect on updating possible array children triggering callbacks that were not updated.
  • Loading branch information
T4rk1n authored Feb 22, 2023
2 parents 3243512 + 72f290f commit b8a993e
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).

## Fixed

- [#2429](https://github.com/plotly/dash/pull/2429) Fix side effect on updating possible array children triggering callbacks, fix [#2411](https://github.com/plotly/dash/issues/2411).
- [#2417](https://github.com/plotly/dash/pull/2417) Disable the pytest plugin if `dash[testing]` not installed, fix [#946](https://github.com/plotly/dash/issues/946).
- [#2417](https://github.com/plotly/dash/pull/2417) Do not swallow the original error to get the webdriver, easier to know what is wrong after updating the browser but the driver.
- [#2425](https://github.com/plotly/dash/pull/2425) Fix multiple log handler added unconditionally to the logger, resulting in duplicate log message.
Expand Down
21 changes: 21 additions & 0 deletions dash/dash-renderer/src/actions/dependencies_ts.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import {
all,
any,
assoc,
concat,
difference,
filter,
flatten,
forEach,
includes,
isEmpty,
keys,
map,
mergeWith,
partition,
path,
pickBy,
props,
reduce,
Expand Down Expand Up @@ -304,6 +307,24 @@ export const getLayoutCallbacks = (
);
}

if (options.filterRoot) {
let rootId = path(['props', 'id'], layout);
if (rootId) {
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
)
);
}
}

/*
Return all callbacks with an `executionGroup` to allow group-processing
*/
Expand Down
12 changes: 8 additions & 4 deletions dash/dash-renderer/src/observers/executedCallbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ const observer: IStoreObserverDefinition<IStoreState> = {
const handlePaths = (
children: any,
oldChildren: any,
oldChildrenPath: any[]
oldChildrenPath: any[],
filterRoot: any = false
) => {
const oPaths = getState().paths;
const paths = computePaths(
Expand All @@ -152,7 +153,8 @@ const observer: IStoreObserverDefinition<IStoreState> = {
requestedCallbacks = concat(
requestedCallbacks,
getLayoutCallbacks(graphs, paths, children, {
chunkPath: oldChildrenPath
chunkPath: oldChildrenPath,
filterRoot
}).map(rcb => ({
...rcb,
predecessors
Expand All @@ -166,7 +168,8 @@ const observer: IStoreObserverDefinition<IStoreState> = {
getLayoutCallbacks(graphs, oldPaths, oldChildren, {
removedArrayInputsOnly: true,
newPaths: paths,
chunkPath: oldChildrenPath
chunkPath: oldChildrenPath,
filterRoot
}).map(rcb => ({
...rcb,
predecessors
Expand Down Expand Up @@ -204,7 +207,8 @@ const observer: IStoreObserverDefinition<IStoreState> = {
}
},
oldObj,
basePath
basePath,
keys(appliedProps)
);
// Only do it once for the component.
recomputed = true;
Expand Down
35 changes: 35 additions & 0 deletions tests/integration/renderer/test_component_as_prop.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,38 @@ def demo(n_clicks):
dash_duo.wait_for_text_to_equal(f"#options label:nth-child({i}) span", "")
dash_duo.wait_for_element(f"#options label:nth-child({i}) button").click()
dash_duo.wait_for_text_to_equal(f"#options label:nth-child({i}) span", "1")


def test_rdcap003_side_effect_regression(dash_duo):
# Test for #2411, regression introduced by original rdcap002 fix
# callback on the same components that is output with same id but not property triggered
# on cap components of array type like Checklist.options[] and Dropdown.options[].
app = Dash(__name__)

app.layout = Div([Button("3<->2", id="a"), Checklist(id="b"), Div(0, id="counter")])

app.clientside_callback(
"function(_, prev) {return parseInt(prev) + 1}",
Output("counter", "children"),
Input("b", "value"),
State("counter", "children"),
prevent_initial_call=True,
)

@app.callback(Output("b", "options"), Input("a", "n_clicks"))
def opts(n):
n_out = 3 - (n or 0) % 2
return [str(i) for i in range(n_out)]

dash_duo.start_server(app)

dash_duo.wait_for_text_to_equal("#counter", "0")
dash_duo.find_element("#a").click()
assert len(dash_duo.find_elements("#b label > input")) == 2
dash_duo.wait_for_text_to_equal("#counter", "0")
dash_duo.find_element("#a").click()
assert len(dash_duo.find_elements("#b label > input")) == 3
dash_duo.wait_for_text_to_equal("#counter", "0")

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

0 comments on commit b8a993e

Please sign in to comment.