Skip to content

Commit 6d7e3d4

Browse files
authored
Merge branch 'dev' into wait-for-none
2 parents 2b7b3ae + 15e97b8 commit 6d7e3d4

File tree

8 files changed

+224
-14
lines changed

8 files changed

+224
-14
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
4+
const AddPropsComponent = (props) => {
5+
const {children, id} = props;
6+
7+
8+
return (
9+
<div id={id}>
10+
{React.cloneElement(children, {
11+
receive: `Element #${id} pass`,
12+
id: id,
13+
})}
14+
</div>
15+
);
16+
};
17+
18+
AddPropsComponent.propTypes = {
19+
id: PropTypes.string,
20+
children: PropTypes.node,
21+
};
22+
23+
export default AddPropsComponent;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
const ReceivePropsComponent = (props) => {
5+
const {id, text, receive} = props;
6+
7+
return (
8+
<div id={id}>
9+
{receive || text}
10+
</div>
11+
);
12+
}
13+
ReceivePropsComponent.propTypes = {
14+
id: PropTypes.string,
15+
text: PropTypes.string,
16+
receive: PropTypes.string,
17+
}
18+
19+
export default ReceivePropsComponent;

@plotly/dash-test-components/src/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import MyPersistedComponentNested from './components/MyPersistedComponentNested'
77
import StyledComponent from './components/StyledComponent';
88
import WidthComponent from './components/WidthComponent';
99
import ComponentAsProp from './components/ComponentAsProp';
10+
import AddPropsComponent from "./components/AddPropsComponent";
11+
import ReceivePropsComponent from "./components/ReceivePropsComponent";
1012

1113
export {
1214
AsyncComponent,
@@ -18,4 +20,6 @@ export {
1820
StyledComponent,
1921
WidthComponent,
2022
ComponentAsProp,
23+
AddPropsComponent,
24+
ReceivePropsComponent
2125
};

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
66

77
## Added
88

9+
- [#2819](https://github.com/plotly/dash/pull/2819) Add dash subcomponents receive additional parameters passed by the parent component. Fixes [#2814](https://github.com/plotly/dash/issues/2814).
910
- [#2826](https://github.com/plotly/dash/pull/2826) When using Pages, allows for `app.title` and (new) `app.description` to be used as defaults for the page title and description. Fixes [#2811](https://github.com/plotly/dash/issues/2811).
1011
- [#2795](https://github.com/plotly/dash/pull/2795) Allow list of components to be passed as layout.
1112
- [#2760](https://github.com/plotly/dash/pull/2760) New additions to dcc.Loading resolving multiple issues:

components/dash-core-components/src/fragments/Dropdown.react.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ const Dropdown = props => {
3939
const {
4040
id,
4141
clearable,
42-
searchable,
4342
multi,
4443
options,
4544
setProps,
45+
search_value,
4646
style,
4747
loading_state,
4848
value,
@@ -122,7 +122,7 @@ const Dropdown = props => {
122122

123123
useEffect(() => {
124124
if (
125-
!searchable &&
125+
!search_value &&
126126
!isNil(sanitizedOptions) &&
127127
optionsCheck !== sanitizedOptions &&
128128
!isNil(value)

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

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import json
22

3-
from dash import Dash, html, dcc, Output, Input
3+
import pytest
4+
5+
from dash import Dash, html, dcc, Output, Input, State
46
from dash.exceptions import PreventUpdate
57

68

@@ -11,7 +13,8 @@
1113
]
1214

1315

14-
def test_ddro001_remove_option_single(dash_dcc):
16+
@pytest.mark.parametrize("searchable", (True, False))
17+
def test_ddro001_remove_option_single(dash_dcc, searchable):
1518
dropdown_options = sample_dropdown_options
1619

1720
app = Dash(__name__)
@@ -22,7 +25,7 @@ def test_ddro001_remove_option_single(dash_dcc):
2225
dcc.Dropdown(
2326
options=dropdown_options,
2427
value=value,
25-
searchable=False,
28+
searchable=searchable,
2629
id="dropdown",
2730
),
2831
html.Button("Remove option", id="remove"),
@@ -38,18 +41,17 @@ def on_click(n_clicks):
3841

3942
@app.callback(Output("value-output", "children"), [Input("dropdown", "value")])
4043
def on_change(val):
41-
if not val:
42-
raise PreventUpdate
43-
return val or "None"
44+
return val or "Nothing Here"
4445

4546
dash_dcc.start_server(app)
4647
btn = dash_dcc.wait_for_element("#remove")
4748
btn.click()
4849

49-
dash_dcc.wait_for_text_to_equal("#value-output", "None")
50+
dash_dcc.wait_for_text_to_equal("#value-output", "Nothing Here")
5051

5152

52-
def test_ddro002_remove_option_multi(dash_dcc):
53+
@pytest.mark.parametrize("searchable", (True, False))
54+
def test_ddro002_remove_option_multi(dash_dcc, searchable):
5355
dropdown_options = sample_dropdown_options
5456

5557
app = Dash(__name__)
@@ -62,7 +64,7 @@ def test_ddro002_remove_option_multi(dash_dcc):
6264
value=value,
6365
multi=True,
6466
id="dropdown",
65-
searchable=False,
67+
searchable=searchable,
6668
),
6769
html.Button("Remove option", id="remove"),
6870
html.Div(id="value-output"),
@@ -84,3 +86,64 @@ def on_change(val):
8486
btn.click()
8587

8688
dash_dcc.wait_for_text_to_equal("#value-output", '["MTL"]')
89+
90+
91+
def test_ddro003_remove_option_multiple_dropdowns(dash_dcc):
92+
app = Dash(__name__)
93+
app.layout = html.Div(
94+
[
95+
dcc.Dropdown(
96+
id="available-options",
97+
multi=True,
98+
options=sample_dropdown_options,
99+
value=["MTL", "NYC", "SF"],
100+
),
101+
dcc.Dropdown(
102+
id="chosen",
103+
multi=True,
104+
options=sample_dropdown_options,
105+
value=["NYC", "SF"],
106+
),
107+
html.Button(id="remove-btn", children="Remove"),
108+
html.Button(id="submit-btn", children="Submit"),
109+
html.Div(id="value-output"),
110+
html.Div(id="options-output"),
111+
],
112+
)
113+
114+
@app.callback(
115+
Output("chosen", "options"),
116+
Input("available-options", "value"),
117+
)
118+
def update_options(available_options):
119+
if available_options is None:
120+
return []
121+
else:
122+
return [{"label": i, "value": i} for i in available_options]
123+
124+
@app.callback(
125+
Output("available-options", "options"), [Input("remove-btn", "n_clicks")]
126+
)
127+
def on_click(n_clicks):
128+
if not n_clicks:
129+
raise PreventUpdate
130+
return sample_dropdown_options[:-1]
131+
132+
@app.callback(
133+
[Output("value-output", "children"), Output("options-output", "children")],
134+
Input("submit-btn", "n_clicks"),
135+
State("chosen", "options"),
136+
State("chosen", "value"),
137+
)
138+
def print_value(n_clicks, options, value):
139+
if not n_clicks:
140+
raise PreventUpdate
141+
return [json.dumps(value), json.dumps([i["value"] for i in options])]
142+
143+
dash_dcc.start_server(app)
144+
btn = dash_dcc.wait_for_element("#remove-btn")
145+
btn.click()
146+
btn = dash_dcc.wait_for_element("#submit-btn")
147+
btn.click()
148+
dash_dcc.wait_for_text_to_equal("#value-output", '["NYC"]')
149+
dash_dcc.wait_for_text_to_equal("#options-output", '["MTL", "NYC"]')

dash/dash-renderer/src/TreeContainer.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
map,
1717
mapObjIndexed,
1818
mergeRight,
19+
omit,
1920
pick,
2021
pickBy,
2122
propOr,
@@ -237,7 +238,13 @@ class BaseTreeContainer extends Component {
237238
);
238239
}
239240

240-
getComponent(_dashprivate_layout, children, loading_state, setProps) {
241+
getComponent(
242+
_dashprivate_layout,
243+
children,
244+
loading_state,
245+
setProps,
246+
_extraProps
247+
) {
241248
const {_dashprivate_config, _dashprivate_dispatch, _dashprivate_error} =
242249
this.props;
243250

@@ -262,7 +269,10 @@ class BaseTreeContainer extends Component {
262269
],
263270
_dashprivate_config
264271
);
265-
let props = dissoc('children', _dashprivate_layout.props);
272+
let props = mergeRight(
273+
_extraProps,
274+
dissoc('children', _dashprivate_layout.props)
275+
);
266276

267277
for (let i = 0; i < childrenProps.length; i++) {
268278
const childrenProp = childrenProps[i];
@@ -481,6 +491,21 @@ class BaseTreeContainer extends Component {
481491
_dashprivate_path
482492
} = this.props;
483493

494+
const _extraProps = omit(
495+
[
496+
'_dashprivate_error',
497+
'_dashprivate_layout',
498+
'_dashprivate_loadingState',
499+
'_dashprivate_loadingStateHash',
500+
'_dashprivate_path',
501+
'_dashprivate_config',
502+
'_dashprivate_dispatch',
503+
'_dashprivate_graphs',
504+
'_dashprivate_loadingMap'
505+
],
506+
this.props
507+
);
508+
484509
const layoutProps = this.getLayoutProps();
485510

486511
const children = this.getChildren(
@@ -492,7 +517,8 @@ class BaseTreeContainer extends Component {
492517
_dashprivate_layout,
493518
children,
494519
_dashprivate_loadingState,
495-
this.setProps
520+
this.setProps,
521+
_extraProps
496522
);
497523
}
498524
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from dash import Dash, html, Input, Output, no_update
2+
3+
from dash_test_components import AddPropsComponent, ReceivePropsComponent
4+
5+
6+
def test_rdarp001_add_receive_props(dash_duo):
7+
app = Dash(__name__)
8+
app.layout = html.Div(
9+
[
10+
AddPropsComponent(
11+
ReceivePropsComponent(
12+
id="test-receive1",
13+
text="receive component1",
14+
),
15+
id="test-add",
16+
),
17+
ReceivePropsComponent(
18+
id="test-receive2",
19+
text="receive component2",
20+
),
21+
html.Button(
22+
"load no pass props",
23+
id="load-no-pass-props",
24+
),
25+
html.Pre("load-no-pass-props-output"),
26+
html.Div(id="load-no-pass-props-output"),
27+
html.Button(
28+
"load pass props",
29+
id="load-pass-props",
30+
),
31+
html.Pre("load-pass-props-output"),
32+
html.Div(id="load-pass-props-output"),
33+
]
34+
)
35+
36+
@app.callback(
37+
Output("load-no-pass-props-output", "children"),
38+
Input("load-no-pass-props", "n_clicks"),
39+
)
40+
def load_no_pass_props(n_clicks):
41+
if n_clicks:
42+
return ReceivePropsComponent(
43+
id="test-receive-no-pass",
44+
text="receive component no pass",
45+
)
46+
return no_update
47+
48+
@app.callback(
49+
Output("load-pass-props-output", "children"),
50+
Input("load-pass-props", "n_clicks"),
51+
)
52+
def load_pass_props(n_clicks):
53+
if n_clicks:
54+
return AddPropsComponent(
55+
ReceivePropsComponent(
56+
id="test-receive-pass",
57+
text="receive component pass",
58+
),
59+
id="test-add-pass",
60+
)
61+
return no_update
62+
63+
dash_duo.start_server(app)
64+
dash_duo.wait_for_text_to_equal("#test-receive1", "Element #test-add pass")
65+
dash_duo.wait_for_text_to_equal("#test-receive2", "receive component2")
66+
67+
clicker_no_pass = dash_duo.wait_for_element("#load-no-pass-props")
68+
clicker_no_pass.click()
69+
dash_duo.wait_for_text_to_equal(
70+
"#test-receive-no-pass", "receive component no pass"
71+
)
72+
clicker_pass = dash_duo.wait_for_element("#load-pass-props")
73+
clicker_pass.click()
74+
dash_duo.wait_for_text_to_equal("#test-receive-pass", "Element #test-add-pass pass")

0 commit comments

Comments
 (0)