Skip to content

Commit e7d95c5

Browse files
authored
Merge pull request #3197 from plotly/fix-optimize-rc4
Fix initial props reset back
2 parents 267cccd + 0bd7d4a commit e7d95c5

File tree

14 files changed

+129
-63
lines changed

14 files changed

+129
-63
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22
All notable changes to `dash` will be documented in this file.
33
This project adheres to [Semantic Versioning](https://semver.org/).
44

5+
## [3.0.0-rc4] - 2025-03-04
6+
7+
## Fixed
8+
9+
- [#3197](https://github.com/plotly/dash/pull/3197) Fix initial props not updated in setProps causing the initial value of props to not be able to be set again.
10+
- [#3183](https://github.com/plotly/dash/pull/3183) Fix external wrapper requiring id.
11+
- [#3184](https://github.com/plotly/dash/pull/3184) Fix devtools dark mode button color issue and other ui fixes for the version checker.
12+
13+
## Changed
14+
15+
- [#3183](https://github.com/plotly/dash/pull/3183) Change ExternalWrapper props to component, componentPath.
16+
- [#3197](https://github.com/plotly/dash/pull/3197) Improved layout path sum stringify of paths.
17+
518
## [3.0.0-rc3] - 2025-02-21
619

720
## Added

components/dash-core-components/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/dash-core-components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dash-core-components",
3-
"version": "3.0.1",
3+
"version": "3.0.2",
44
"description": "Core component suite for Dash",
55
"repository": {
66
"type": "git",

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const Dropdown = props => {
4545
search_value,
4646
style,
4747
value,
48+
searchable,
4849
} = props;
4950
const [optionsCheck, setOptionsCheck] = useState(null);
5051
const persistentOptions = useRef(null);
@@ -157,7 +158,7 @@ const Dropdown = props => {
157158
options={sanitizedOptions}
158159
value={value}
159160
onChange={onChange}
160-
onInputChange={onInputChange}
161+
onInputChange={searchable ? onInputChange : undefined}
161162
backspaceRemoves={clearable}
162163
deleteRemoves={clearable}
163164
inputProps={{autoComplete: 'off'}}

dash/_dash_renderer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22

3-
__version__ = "2.0.2"
3+
__version__ = "2.0.3"
44

55
_available_react_versions = {"18.3.1", "18.2.0", "16.14.0"}
66
_available_reactdom_versions = {"18.3.1", "18.2.0", "16.14.0"}
@@ -64,7 +64,7 @@ def _set_react_version(v_react, v_reactdom=None):
6464
{
6565
"relative_package_path": "dash-renderer/build/dash_renderer.min.js",
6666
"dev_package_path": "dash-renderer/build/dash_renderer.dev.js",
67-
"external_url": "https://unpkg.com/[email protected].2"
67+
"external_url": "https://unpkg.com/[email protected].3"
6868
"/build/dash_renderer.min.js",
6969
"namespace": "dash",
7070
},

dash/dash-renderer/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dash/dash-renderer/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dash-renderer",
3-
"version": "2.0.2",
3+
"version": "2.0.3",
44
"description": "render dash components in react",
55
"main": "build/dash_renderer.min.js",
66
"scripts": {

dash/dash-renderer/src/reducers/reducer.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import layout from './layout';
1818
import paths from './paths';
1919
import callbackJobs from './callbackJobs';
2020
import loading from './loading';
21+
import {stringifyPath} from '../wrapper/wrapping';
2122

2223
export const apiRequests = [
2324
'dependenciesRequest',
@@ -36,9 +37,9 @@ function layoutHashes(state = {}, action) {
3637
) {
3738
// Let us compare the paths sums to get updates without triggering
3839
// render on the parent containers.
39-
const jsonPath = JSON.stringify(action.payload.itempath);
40-
const prev = pathOr(0, [jsonPath], state);
41-
return assoc(jsonPath, prev + 1, state);
40+
const strPath = stringifyPath(action.payload.itempath);
41+
const prev = pathOr(0, [strPath], state);
42+
return assoc(strPath, prev + 1, state);
4243
}
4344
return state;
4445
}

dash/dash-renderer/src/wrapper/DashWrapper.tsx

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {DashConfig} from '../config';
2424
import {notifyObservers, onError, updateProps} from '../actions';
2525
import {getWatchedKeys, stringifyId} from '../actions/dependencies';
2626
import {recordUiEdit} from '../persistence';
27-
import {createElement, isDryComponent} from './wrapping';
27+
import {createElement, getComponentLayout, isDryComponent} from './wrapping';
2828
import Registry from '../registry';
2929
import isSimpleComponent from '../isSimpleComponent';
3030
import {
@@ -62,56 +62,61 @@ function DashWrapper({
6262
const setProps = (newProps: UpdatePropsPayload) => {
6363
const {id} = componentProps;
6464
const {_dash_error, ...restProps} = newProps;
65-
const oldProps = componentProps;
66-
const changedProps = pickBy(
67-
(val, key) => !equals(val, oldProps[key]),
68-
restProps
69-
);
70-
if (_dash_error) {
71-
dispatch(
72-
onError({
73-
type: 'frontEnd',
74-
error: _dash_error
75-
})
65+
66+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
67+
// @ts-ignore
68+
dispatch((dispatch, getState) => {
69+
const currentState = getState();
70+
const {graphs} = currentState;
71+
72+
const {props: oldProps} = getComponentLayout(
73+
componentPath,
74+
currentState
7675
);
77-
}
78-
if (!isEmpty(changedProps)) {
79-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
80-
// @ts-ignore
81-
dispatch((dispatch, getState) => {
82-
const {graphs} = getState();
83-
// Identify the modified props that are required for callbacks
84-
const watchedKeys = getWatchedKeys(
85-
id,
86-
keys(changedProps),
87-
graphs
76+
const changedProps = pickBy(
77+
(val, key) => !equals(val, oldProps[key]),
78+
restProps
79+
);
80+
if (_dash_error) {
81+
dispatch(
82+
onError({
83+
type: 'frontEnd',
84+
error: _dash_error
85+
})
8886
);
87+
}
8988

90-
batch(() => {
91-
// setProps here is triggered by the UI - record these changes
92-
// for persistence
93-
recordUiEdit(component, newProps, dispatch);
89+
if (isEmpty(changedProps)) {
90+
return;
91+
}
9492

95-
// Only dispatch changes to Dash if a watched prop changed
96-
if (watchedKeys.length) {
97-
dispatch(
98-
notifyObservers({
99-
id,
100-
props: pick(watchedKeys, changedProps)
101-
})
102-
);
103-
}
93+
// Identify the modified props that are required for callbacks
94+
const watchedKeys = getWatchedKeys(id, keys(changedProps), graphs);
95+
96+
batch(() => {
97+
// setProps here is triggered by the UI - record these changes
98+
// for persistence
99+
recordUiEdit(component, newProps, dispatch);
104100

105-
// Always update this component's props
101+
// Only dispatch changes to Dash if a watched prop changed
102+
if (watchedKeys.length) {
106103
dispatch(
107-
updateProps({
108-
props: changedProps,
109-
itempath: componentPath
104+
notifyObservers({
105+
id,
106+
props: pick(watchedKeys, changedProps)
110107
})
111108
);
112-
});
109+
}
110+
111+
// Always update this component's props
112+
dispatch(
113+
updateProps({
114+
props: changedProps,
115+
itempath: componentPath
116+
})
117+
);
113118
});
114-
}
119+
});
115120
};
116121

117122
const createContainer = useCallback(

dash/dash-renderer/src/wrapper/selectors.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
1-
import {path} from 'ramda';
2-
31
import {DashLayoutPath, DashComponent, BaseDashProps} from '../types/component';
2+
import {getComponentLayout, stringifyPath} from './wrapping';
43

54
type SelectDashProps = [DashComponent, BaseDashProps, number];
65

76
export const selectDashProps =
87
(componentPath: DashLayoutPath) =>
98
(state: any): SelectDashProps => {
10-
const c = path(componentPath, state.layout) as DashComponent;
9+
const c = getComponentLayout(componentPath, state);
1110
// Layout hashes records the number of times a path has been updated.
1211
// sum with the parents hash (match without the last ']') to get the real hash
1312
// Then it can be easily compared without having to compare the props.
14-
let jsonPath = JSON.stringify(componentPath);
15-
jsonPath = jsonPath.substring(0, jsonPath.length - 1);
13+
const strPath = stringifyPath(componentPath);
1614

1715
const h = Object.entries(state.layoutHashes).reduce(
18-
(acc, [path, pathHash]) =>
19-
jsonPath.startsWith(path.substring(0, path.length - 1))
16+
(acc, [updatedPath, pathHash]) =>
17+
strPath.startsWith(updatedPath)
2018
? (pathHash as number) + acc
2119
: acc,
2220
0

dash/dash-renderer/src/wrapper/wrapping.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
2-
import {mergeRight, type, has} from 'ramda';
2+
import {mergeRight, path, type, has, join} from 'ramda';
3+
import {DashComponent, DashLayoutPath} from '../types/component';
34

45
export function createElement(
56
element: any,
@@ -49,3 +50,14 @@ export function validateComponent(componentDefinition: any) {
4950
);
5051
}
5152
}
53+
54+
export function stringifyPath(layoutPath: DashLayoutPath) {
55+
return join(',', layoutPath);
56+
}
57+
58+
export function getComponentLayout(
59+
componentPath: DashLayoutPath,
60+
state: any
61+
): DashComponent {
62+
return path(componentPath, state.layout) as DashComponent;
63+
}

dash/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "3.0.0rc3"
1+
__version__ = "3.0.0rc4"

tests/integration/renderer/test_persistence.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from multiprocessing import Value
2+
import flaky
23
import pytest
34
import time
45

@@ -206,6 +207,7 @@ def toggle_table(n):
206207
check_table_names(dash_duo, ["a", "b"])
207208

208209

210+
@flaky.flaky(max_runs=3)
209211
def test_rdps005_persisted_props(dash_duo):
210212
app = Dash(__name__)
211213
app.layout = html.Div(

tests/integration/test_integration.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,3 +472,37 @@ def my_route_f():
472472
response = requests.post(url)
473473
assert response.status_code == 200
474474
assert response.text == "hello"
475+
476+
477+
def test_inin031_initial_value_set_back(dash_duo):
478+
# Test for regression on the initial value to be able to
479+
# set back to initial after changing again.
480+
app = Dash(__name__)
481+
482+
app.layout = html.Div(
483+
[
484+
dcc.Dropdown(
485+
id="dropdown",
486+
options=["Toronto", "Montréal", "Vancouver"],
487+
value="Toronto",
488+
searchable=False,
489+
),
490+
html.Div(id="output"),
491+
]
492+
)
493+
494+
@app.callback(Output("output", "children"), [Input("dropdown", "value")])
495+
def callback(value):
496+
return f"You have selected {value}"
497+
498+
dash_duo.start_server(app)
499+
500+
dash_duo.wait_for_text_to_equal("#output", "You have selected Toronto")
501+
502+
dash_duo.select_dcc_dropdown("#dropdown", "Vancouver")
503+
dash_duo.wait_for_text_to_equal("#output", "You have selected Vancouver")
504+
505+
dash_duo.select_dcc_dropdown("#dropdown", "Toronto")
506+
dash_duo.wait_for_text_to_equal("#output", "You have selected Toronto")
507+
508+
assert dash_duo.get_logs() == []

0 commit comments

Comments
 (0)