Fix dcc.Patch() disturbing pre-existing components (re-run callbacks + wiped persistence) - #3938
Open
Aaron-Wrote-This wants to merge 3 commits into
Conversation
* As each Patch() operation is applied, record which component ids are created and which props are written * Store one PatchAnalysis per output that returned a Patch, keyed by the output's id, on the callback result's patchedOutputs map * To be used to for tracking what the Patch actually created vs elements carried over unchanged
* Before, when a Patch added or rebuilt a container's children list,
getUnfilteredLayoutCallbacks re-fired the initial call for every
MATCH/ALL callback bound to a component, even if it already existed
before the Patch
* Now, initial calls are gated on Patch operations actually change
* patchAnalysis.ts records each patch operation and which component
ids are being created
* handleOneId suppresses the initial call only for ids the patch did
not create. This also correctly fires for a component rebuilt with
an id that was already in use, even when its new defaults happen to
coincide with the prior occupant's values
* Add regression tests test_wildcards: 11, 12, 13
* applyPersistence ran unconditionally on every component reachable from a Patch result, including ones the Patch carried over unchanged For a persisted component, that meant the just-carried-over value was treated as a fresh server default and overwrote the user's stored edit * Now skip persistence restoration, for components the patch did not create, by having persistenceMods consults the PatchAnalysis via isUntouchedByPatch, so a component genuinely carried over keeps its persisted value, while one rebuilt with a reused id still gets its persisted value restored * * Add regression tests to test_persistence: 15, 16, 17
Aaron-Wrote-This
requested review from
KoolADE85,
T4rk1n,
camdecoster and
ndrezn
as code owners
August 3, 2026 03:09
|
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Fixes #3681 and #3937
Problem
When a
dcc.Patch()added or rebuilt components inside a container, two separate bugs trigger,effecting components which already existed before the Patch:
MATCH/ALLoutput, so appending one newpattern matching id'd
dcc.Sliderreruns the initial callback for every preexisting sliderbecause the patch added a new sibling
I tracked both items back to the way Patch resolution distinguishes components carried over from the
pre-Patch layout from freshly built ones
Fix
Snapshot the paths table before a Patch is resolved
Callbacks can suppress the initial call for carried-over components, by detecting props
reference identity (
child.props === oldPropsRef). ramda'sassocPathis structurally immutable,so untouched nodes keep their exact
propsreference while rebuilt/replaced node gets a new oneapplyPersistencenow skips components that already existed pre-Patch, instead of re-runningmodPropand misreading the carried over value as a "server override" that clears the stored editContributor Checklist
I've been doing a lot of work with dynamic UIs and creating extensible elements. I am loving the patch system, but I found some significant drawbacks to the both in performance and usability.
At first, I thought the fix for the initial callbacks was simple and just checking the ID more carefully would be enough, but it ended up being requiring adding an 'audit trail' for patches, which also ended up fixing the persistence issues.
Javascript isn't my 'first language', so let me know if there are any things I'm doing awkwardly, or best practices I'm missing. Happy to fix those up.
Or I understand if this is too complicated and not exactly needed. I looked for simpler options, but I couldn't find a way to differentiate patches and understand their impact in any other way, but it would be great if one existed and I just didn't see it.
optionals
CHANGELOG.md