diff --git a/CHANGELOG.md b/CHANGELOG.md index bf400201bf..418bdfe7cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). - [#3353](https://github.com/plotly/dash/pull/3353) Support pattern-matching/dict ids in `dcc.Loading` `target_components` - [#3371](https://github.com/plotly/dash/pull/3371) Fix allow_optional triggering a warning for not found input. - [#3379](https://github.com/plotly/dash/pull/3379) Fix dcc.Graph backward compatibility with dash 2.0 for ddk.Graph +- [#3373](https://github.com/plotly/dash/pull/3373) Fix layout as list and persistence. # [3.1.1] - 2025-06-29 diff --git a/dash/dash-renderer/src/persistence.js b/dash/dash-renderer/src/persistence.js index c58bb4b916..a029736866 100644 --- a/dash/dash-renderer/src/persistence.js +++ b/dash/dash-renderer/src/persistence.js @@ -70,6 +70,7 @@ import {createAction} from 'redux-actions'; import Registry from './registry'; import {stringifyId} from './actions/dependencies'; +import {isDryComponent} from './wrapper/wrapping'; export const storePrefix = '_dash_persistence.'; @@ -361,10 +362,11 @@ export function recordUiEdit(layout, newProps, dispatch) { * callbacks) to apply previously-stored UI edits to components */ export function applyPersistence(layout, dispatch) { - if (type(layout) !== 'Object' || !layout.props) { - return layout; + if (Array.isArray(layout)) { + return layout.map(lay => + isDryComponent(lay) ? persistenceMods(lay, lay, [], dispatch) : lay + ); } - return persistenceMods(layout, layout, [], dispatch); } diff --git a/tests/integration/renderer/test_persistence.py b/tests/integration/renderer/test_persistence.py index 5aff9d5c33..d053fff12d 100644 --- a/tests/integration/renderer/test_persistence.py +++ b/tests/integration/renderer/test_persistence.py @@ -560,3 +560,45 @@ def update_container2(n_clicks): # persistenceTransforms should return upper case strings dash_duo.wait_for_text_to_equal("#component-propName", "ALPACA") dash_duo.wait_for_text_to_equal("#component-propPart", "ARTICHOKE") + + +def test_rdps014_layout_as_list(dash_duo): + # testing persistence with layout as list + app = dash.Dash(__name__) + app.layout = [ + dcc.Input(id="input-1", value="initial", persistence=True), + html.Div(id="output-1"), + dcc.Input(id="input-2", value="second", persistence=True), + html.Div(id="output-2"), + ] + + @app.callback(Output("output-1", "children"), [Input("input-1", "value")]) + def update_output_1(value): + return f"Output 1: {value}" + + @app.callback(Output("output-2", "children"), [Input("input-2", "value")]) + def update_output_2(value): + return f"Output 2: {value}" + + dash_duo.start_server(app) + + # Check initial values + dash_duo.wait_for_text_to_equal("#output-1", "Output 1: initial") + dash_duo.wait_for_text_to_equal("#output-2", "Output 2: second") + + # Change the input values + dash_duo.clear_input("#input-1") + dash_duo.find_element("#input-1").send_keys("changed1") + dash_duo.clear_input("#input-2") + dash_duo.find_element("#input-2").send_keys("changed2") + + # Verify changes + dash_duo.wait_for_text_to_equal("#output-1", "Output 1: changed1") + dash_duo.wait_for_text_to_equal("#output-2", "Output 2: changed2") + + # Reload the page to test persistence + dash_duo.wait_for_page() + + # Check that persisted values are restored + dash_duo.wait_for_text_to_equal("#output-1", "Output 1: changed1") + dash_duo.wait_for_text_to_equal("#output-2", "Output 2: changed2")