From cc6522c4db45435fd685d111e3b4d527aafe5f30 Mon Sep 17 00:00:00 2001 From: petar-qb Date: Wed, 16 Apr 2025 14:16:11 +0200 Subject: [PATCH 1/5] Store callback Output value in persistence storage --- dash/dash-renderer/src/actions/index.js | 15 +++++- dash/dash-renderer/src/persistence.js | 53 +++++-------------- .../dash-renderer/src/wrapper/DashWrapper.tsx | 5 -- 3 files changed, 27 insertions(+), 46 deletions(-) diff --git a/dash/dash-renderer/src/actions/index.js b/dash/dash-renderer/src/actions/index.js index 86649c2d7d..91654fa75f 100644 --- a/dash/dash-renderer/src/actions/index.js +++ b/dash/dash-renderer/src/actions/index.js @@ -1,4 +1,4 @@ -import {once} from 'ramda'; +import {once, path} from 'ramda'; import {createAction} from 'redux-actions'; import {addRequestedCallbacks} from './callbacks'; import {getAppState} from '../reducers/constants'; @@ -7,6 +7,7 @@ import cookie from 'cookie'; import {validateCallbacksToLayout} from './dependencies'; import {includeObservers, getLayoutCallbacks} from './dependencies_ts'; import {computePaths, getPath} from './paths'; +import {recordUiEdit} from '../persistence'; export const onError = createAction(getAction('ON_ERROR')); export const setAppLifecycle = createAction(getAction('SET_APP_LIFECYCLE')); @@ -17,10 +18,20 @@ export const setHooks = createAction(getAction('SET_HOOKS')); export const setLayout = createAction(getAction('SET_LAYOUT')); export const setPaths = createAction(getAction('SET_PATHS')); export const setRequestQueue = createAction(getAction('SET_REQUEST_QUEUE')); -export const updateProps = createAction(getAction('ON_PROP_CHANGE')); export const insertComponent = createAction(getAction('INSERT_COMPONENT')); export const removeComponent = createAction(getAction('REMOVE_COMPONENT')); +// Change the variable name of the action +export const onPropChange = createAction(getAction('ON_PROP_CHANGE')); + +export function updateProps(payload) { + return (dispatch, getState) => { + const component = path(payload.itempath, getState().layout); + recordUiEdit(component, payload.props, dispatch); + dispatch(onPropChange(payload)); + }; +} + export const addComponentToLayout = payload => (dispatch, getState) => { const {paths} = getState(); dispatch(insertComponent(payload)); diff --git a/dash/dash-renderer/src/persistence.js b/dash/dash-renderer/src/persistence.js index 1698c09972..7da1bb2272 100644 --- a/dash/dash-renderer/src/persistence.js +++ b/dash/dash-renderer/src/persistence.js @@ -501,46 +501,21 @@ export function prunePersistence(layout, newProps, dispatch) { depersistedProps = mergeRight(props, update); } - if (finalPersistence) { + if (finalPersistence && persistenceChanged) { const finalStorage = getStore(finalPersistenceType, dispatch); - - if (persistenceChanged) { - // apply new persistence - forEach( - persistedProp => - modProp( - getValsKey(id, persistedProp, finalPersistence), - finalStorage, - element, - depersistedProps, - persistedProp, - update - ), - filter(notInNewProps, finalPersistedProps) - ); - } - - // now the main point - clear any edit of a prop that changed - // note that this is independent of the new prop value. - const transforms = element.persistenceTransforms || {}; - for (const propName in newProps) { - const propTransforms = transforms[propName]; - if (propTransforms) { - for (const propPart in propTransforms) { - finalStorage.removeItem( - getValsKey( - id, - `${propName}.${propPart}`, - finalPersistence - ) - ); - } - } else { - finalStorage.removeItem( - getValsKey(id, propName, finalPersistence) - ); - } - } + // apply new persistence + forEach( + persistedProp => + modProp( + getValsKey(id, persistedProp, finalPersistence), + finalStorage, + element, + depersistedProps, + persistedProp, + update + ), + filter(notInNewProps, finalPersistedProps) + ); } return persistenceChanged ? mergeRight(newProps, update) : newProps; } diff --git a/dash/dash-renderer/src/wrapper/DashWrapper.tsx b/dash/dash-renderer/src/wrapper/DashWrapper.tsx index 370c385ba5..dd81bcf01e 100644 --- a/dash/dash-renderer/src/wrapper/DashWrapper.tsx +++ b/dash/dash-renderer/src/wrapper/DashWrapper.tsx @@ -23,7 +23,6 @@ import {DashLayoutPath, UpdatePropsPayload} from '../types/component'; import {DashConfig} from '../config'; import {notifyObservers, onError, updateProps} from '../actions'; import {getWatchedKeys, stringifyId} from '../actions/dependencies'; -import {recordUiEdit} from '../persistence'; import { createElement, getComponentLayout, @@ -131,10 +130,6 @@ function DashWrapper({ const watchedKeys = getWatchedKeys(id, keys(changedProps), graphs); batch(() => { - // setProps here is triggered by the UI - record these changes - // for persistence - recordUiEdit(renderComponent, newProps, dispatch); - // Only dispatch changes to Dash if a watched prop changed if (watchedKeys.length) { dispatch( From 11f94f250af5d85c595686d79e48f4e68dfa9d0c Mon Sep 17 00:00:00 2001 From: petar-qb Date: Wed, 16 Apr 2025 15:17:14 +0200 Subject: [PATCH 2/5] Linting minor change --- dash/dash-renderer/src/observers/executedCallbacks.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dash/dash-renderer/src/observers/executedCallbacks.ts b/dash/dash-renderer/src/observers/executedCallbacks.ts index f82a24bdf0..85d97299bc 100644 --- a/dash/dash-renderer/src/observers/executedCallbacks.ts +++ b/dash/dash-renderer/src/observers/executedCallbacks.ts @@ -11,6 +11,9 @@ import { pathOr } from 'ramda'; +import {ThunkDispatch} from 'redux-thunk'; +import {AnyAction} from 'redux'; + import {IStoreState} from '../store'; import { @@ -63,7 +66,7 @@ const observer: IStoreObserverDefinition = { // In case the update contains whole components, see if any of // those components have props to update to persist user edits. const {props} = applyPersistence({props: updatedProps}, dispatch); - dispatch( + (dispatch as ThunkDispatch)( updateProps({ itempath, props, From e1327a8d4f6d6513d3bb50bc6675e82e1b2511f6 Mon Sep 17 00:00:00 2001 From: petar-qb Date: Thu, 17 Apr 2025 14:15:13 +0200 Subject: [PATCH 3/5] Removing unnecessary comment --- dash/dash-renderer/src/actions/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/dash/dash-renderer/src/actions/index.js b/dash/dash-renderer/src/actions/index.js index 91654fa75f..fd8c314d78 100644 --- a/dash/dash-renderer/src/actions/index.js +++ b/dash/dash-renderer/src/actions/index.js @@ -21,7 +21,6 @@ export const setRequestQueue = createAction(getAction('SET_REQUEST_QUEUE')); export const insertComponent = createAction(getAction('INSERT_COMPONENT')); export const removeComponent = createAction(getAction('REMOVE_COMPONENT')); -// Change the variable name of the action export const onPropChange = createAction(getAction('ON_PROP_CHANGE')); export function updateProps(payload) { From 72cdc37f60ece06ab16555120e01d6256800d042 Mon Sep 17 00:00:00 2001 From: petar-qb Date: Tue, 29 Apr 2025 15:38:55 +0200 Subject: [PATCH 4/5] Fix rdps011 failing test --- dash/dash-renderer/src/persistence.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/dash/dash-renderer/src/persistence.js b/dash/dash-renderer/src/persistence.js index 7da1bb2272..c58bb4b916 100644 --- a/dash/dash-renderer/src/persistence.js +++ b/dash/dash-renderer/src/persistence.js @@ -318,7 +318,14 @@ export function recordUiEdit(layout, newProps, dispatch) { persisted_props, persistence_type } = getProps(layout); - if (!canPersist || !persistence) { + + // if the "persistence" property is changed as a callback output, + // skip the persistence storage overwriting. + const isPersistenceMismatch = + newProps?.persistence !== undefined && + newProps.persistence !== persistence; + + if (!canPersist || !persistence || isPersistenceMismatch) { return; } From fc5eea6268b71cbfe2cef36dc162429cd7e8393b Mon Sep 17 00:00:00 2001 From: petar-qb Date: Tue, 29 Apr 2025 17:31:01 +0200 Subject: [PATCH 5/5] Adding a changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbdb5fa317..ed2ea067fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). + +## [UNRELEASED] + +## Fixed +- [#3279](https://github.com/plotly/dash/pull/3279) Fix an issue where persisted values were incorrectly pruned when updated via callback. Now, callback returned values are correctly stored in the persistence storage. Fix [#2678](https://github.com/plotly/dash/issues/2678) + ## [3.0.4] - 2025-04-24 ## Fixed