Skip to content

Commit 308dece

Browse files
authored
Merge pull request #1248 from plotly/1245-persist-patterns
support persistence with dict ids
2 parents 79c6f30 + 20cf65f commit 308dece

File tree

4 files changed

+85
-6
lines changed

4 files changed

+85
-6
lines changed

Diff for: CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
99
### Changed
1010
- [#1237](https://github.com/plotly/dash/pull/1237) Closes [#920](https://github.com/plotly/dash/issues/920): Converts hot reload fetch failures into a server status indicator showing whether the latest fetch succeeded or failed. Callback fetch failures still appear as errors but have a clearer message.
1111

12+
### Fixed
13+
- [#1248](https://github.com/plotly/dash/pull/1248) Fixes [#1245](https://github.com/plotly/dash/issues/1245), so you can use prop persistence with components that have dict IDs, ie for pattern-matching callbacks.
14+
1215
## [1.12.0] - 2020-05-05
1316
### Added
1417
- [#1228](https://github.com/plotly/dash/pull/1228) Adds control over firing callbacks on page (or layout chunk) load. Individual callbacks can have their initial calls disabled in their definition `@app.callback(..., prevent_initial_call=True)` and similar for `app.clientside_callback`. The app-wide default can also be changed with `app=Dash(prevent_initial_callbacks=True)`, then individual callbacks may disable this behavior.

Diff for: dash-renderer/src/persistence.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import {
6969
import {createAction} from 'redux-actions';
7070

7171
import Registry from './registry';
72+
import {stringifyId} from './actions/dependencies';
7273

7374
export const storePrefix = '_dash_persistence.';
7475

@@ -270,7 +271,7 @@ const getTransform = (element, propName, propPart) =>
270271
: noopTransform;
271272

272273
const getValsKey = (id, persistedProp, persistence) =>
273-
`${id}.${persistedProp}.${JSON.stringify(persistence)}`;
274+
`${stringifyId(id)}.${persistedProp}.${JSON.stringify(persistence)}`;
274275

275276
const getProps = layout => {
276277
const {props, type, namespace} = layout;

Diff for: tests/integration/devtools/test_hot_reload.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616

1717
def test_dvhr001_hot_reload(dash_duo):
1818
app = dash.Dash(__name__, assets_folder="hr_assets")
19-
app.layout = html.Div([
20-
html.H3("Hot reload", id="text"),
21-
html.Button("Click", id="btn")
22-
], id="hot-reload-content")
19+
app.layout = html.Div(
20+
[html.H3("Hot reload", id="text"), html.Button("Click", id="btn")],
21+
id="hot-reload-content",
22+
)
2323

2424
@app.callback(Output("text", "children"), [Input("btn", "n_clicks")])
2525
def new_text(n):

Diff for: tests/integration/renderer/test_persistence.py

+76-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from selenium.webdriver.common.keys import Keys
66

77
import dash
8-
from dash.dependencies import Input, Output
8+
from dash.dependencies import Input, Output, State, MATCH
99

1010
import dash_core_components as dcc
1111
import dash_html_components as html
@@ -451,3 +451,78 @@ def set_out(val):
451451
dash_duo.find_element("#persistence-val").send_keys("2")
452452
assert not dash_duo.get_logs()
453453
dash_duo.wait_for_text_to_equal("#out", "artichoke")
454+
455+
456+
def test_rdps012_pattern_matching(dash_duo):
457+
# copy of rdps010 but with dict IDs,
458+
# plus a button to change the dict ID so the persistence should reset
459+
def make_input(persistence, n):
460+
return dcc.Input(
461+
id={"i": n, "id": "persisted"},
462+
className="persisted",
463+
value="a",
464+
persistence=persistence,
465+
)
466+
467+
app = dash.Dash(__name__)
468+
app.layout = html.Div(
469+
[html.Button("click", id="btn", n_clicks=0), html.Div(id="content")]
470+
)
471+
472+
@app.callback(Output("content", "children"), [Input("btn", "n_clicks")])
473+
def content(n):
474+
return [
475+
dcc.Input(
476+
id={"i": n, "id": "persistence-val"},
477+
value="",
478+
className="persistence-val",
479+
),
480+
html.Div(make_input("", n), id={"i": n, "id": "persisted-container"}),
481+
html.Div(id={"i": n, "id": "out"}, className="out"),
482+
]
483+
484+
@app.callback(
485+
Output({"i": MATCH, "id": "persisted-container"}, "children"),
486+
[Input({"i": MATCH, "id": "persistence-val"}, "value")],
487+
[State("btn", "n_clicks")],
488+
)
489+
def set_persistence(val, n):
490+
return make_input(val, n)
491+
492+
@app.callback(
493+
Output({"i": MATCH, "id": "out"}, "children"),
494+
[Input({"i": MATCH, "id": "persisted"}, "value")],
495+
)
496+
def set_out(val):
497+
return val
498+
499+
dash_duo.start_server(app)
500+
501+
for _ in range(3):
502+
dash_duo.wait_for_text_to_equal(".out", "a")
503+
dash_duo.find_element(".persisted").send_keys("lpaca")
504+
dash_duo.wait_for_text_to_equal(".out", "alpaca")
505+
506+
dash_duo.find_element(".persistence-val").send_keys("s")
507+
dash_duo.wait_for_text_to_equal(".out", "a")
508+
dash_duo.find_element(".persisted").send_keys("nchovies")
509+
dash_duo.wait_for_text_to_equal(".out", "anchovies")
510+
511+
dash_duo.find_element(".persistence-val").send_keys("2")
512+
dash_duo.wait_for_text_to_equal(".out", "a")
513+
dash_duo.find_element(".persisted").send_keys(
514+
Keys.BACK_SPACE
515+
) # persist falsy value
516+
dash_duo.wait_for_text_to_equal(".out", "")
517+
518+
# alpaca not saved with falsy persistence
519+
dash_duo.clear_input(".persistence-val")
520+
dash_duo.wait_for_text_to_equal(".out", "a")
521+
522+
# anchovies and aardvark saved
523+
dash_duo.find_element(".persistence-val").send_keys("s")
524+
dash_duo.wait_for_text_to_equal(".out", "anchovies")
525+
dash_duo.find_element(".persistence-val").send_keys("2")
526+
dash_duo.wait_for_text_to_equal(".out", "")
527+
528+
dash_duo.find_element("#btn").click()

0 commit comments

Comments
 (0)