Skip to content

Fix inserted dynamic ids in component as props. #2336

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

- [#2332](https://github.com/plotly/dash/pull/2332) Add key to wrapped children props in list.
- [#2336](https://github.com/plotly/dash/pull/2336) Fix inserted dynamic ids in component as props.

## [2.7.0] - 2022-11-03

Expand Down
86 changes: 74 additions & 12 deletions dash/dash-renderer/src/observers/executedCallbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
path,
forEach,
keys,
has,
pickBy,
toPairs
toPairs,
pathOr
} from 'ramda';

import {IStoreState} from '../store';
Expand Down Expand Up @@ -126,20 +126,25 @@ const observer: IStoreObserverDefinition<IStoreState> = {
}))
);

// New layout - trigger callbacks for that explicitly
if (has('children', appliedProps)) {
const {children} = appliedProps;
const basePath = getPath(oldPaths, parsedId);
const oldObj = path(getPath(oldPaths, parsedId), oldLayout);

const oldChildrenPath: string[] = concat(
getPath(oldPaths, parsedId) as string[],
['props', 'children']
);
const oldChildren = path(oldChildrenPath, oldLayout);
const childrenProps = pathOr(
'defaultValue',
[oldObj.namespace, oldObj.type],
(window as any).__dashprivate_childrenProps
);

const handlePaths = (
children: any,
oldChildren: any,
oldChildrenPath: any[]
) => {
const oPaths = getState().paths;
const paths = computePaths(
children,
oldChildrenPath,
oldPaths
oPaths
);
dispatch(setPaths(paths));

Expand Down Expand Up @@ -167,7 +172,64 @@ const observer: IStoreObserverDefinition<IStoreState> = {
predecessors
}))
);
}
};

let recomputed = false;

forEach(childrenProp => {
if (recomputed) {
return;
}
if (childrenProp.includes('[]')) {
const [frontPath] = childrenProp
.split('[]')
.map(p => p.split('.').filter(e => e));

const frontObj: any[] | undefined = path(
frontPath,
appliedProps
);

if (!frontObj) {
return;
}

// Crawl layout needs the ns/type
handlePaths(
{
...oldObj,
props: {
...oldObj.props,
...appliedProps
}
},
oldObj,
basePath
);
// Only do it once for the component.
recomputed = true;
} else {
const childrenPropPath = childrenProp.split('.');
const children = path(
childrenPropPath,
appliedProps
);
if (!children) {
return;
}

const oldChildrenPath = concat(
getPath(oldPaths, parsedId) as string[],
['props'].concat(childrenPropPath)
);
const oldChildren = path(
oldChildrenPath,
oldLayout
);

handlePaths(children, oldChildren, oldChildrenPath);
}
}, ['children'].concat(childrenProps));

// persistence edge case: if you explicitly update the
// persistence key, other props may change that require us
Expand Down
54 changes: 53 additions & 1 deletion tests/integration/renderer/test_component_as_prop.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
from dash import Dash, Input, Output, callback_context
import uuid

from dash import Dash, Input, Output, callback_context, State, MATCH

from dash_test_components import ComponentAsProp

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


def opt(u):
return {
"label": [
Button(
"click me", id={"type": "button", "index": u}, className="label-button"
),
Span(id={"type": "text", "index": u}, className="label-result"),
],
"value": u,
}


def test_rdcap001_component_as_prop(dash_duo):
app = Dash(__name__)

Expand Down Expand Up @@ -206,3 +222,39 @@ def updated_from_list(*_):
dash_duo.wait_for_text_to_equal("#multi2", "foo - bar")

assert dash_duo.get_logs() == []


def test_rdcap002_component_as_props_dynamic_id(dash_duo):
# Test for issue 2296
app = Dash(__name__)
n = 3
app.layout = Div(
[
Button("add options", id="add-option", style={"marginBottom": "25px"}),
Checklist([opt(str(uuid.uuid4())) for i in range(n)], id="options"),
]
)

@app.callback(
Output("options", "options"),
Input("add-option", "n_clicks"),
State("options", "options"),
prevent_initial_call=True,
)
def add_option(_, options):
return [*options, opt(str(uuid.uuid4()))]

@app.callback(
Output({"type": "text", "index": MATCH}, "children"),
Input({"type": "button", "index": MATCH}, "n_clicks"),
)
def demo(n_clicks):
return n_clicks

dash_duo.start_server(app)

dash_duo.wait_for_element("#add-option").click()
for i in range(1, n + 2):
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")