Skip to content

Preserve state on hot reload - #3896

Open
T4rk1n wants to merge 3 commits into
devfrom
state-preserving-hot-reload
Open

Preserve state on hot reload#3896
T4rk1n wants to merge 3 commits into
devfrom
state-preserving-hot-reload

Conversation

@T4rk1n

@T4rk1n T4rk1n commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Add dev_tools_hot_reload_preserve_state: keep UI state across hot reloads

When enabled, the renderer records UI-driven prop edits, set_props
updates (clientside, serverside and websocket) and memory-type dcc.Store
writes as [newVal, originalVal] pairs. Just before a hot reload - soft
RELOAD dispatch or full page reload - they are snapshotted (in memory +
sessionStorage), then re-applied to the incoming layout unless a prop's
initial value changed in the reloaded code, in which case the new code
wins. Unmatched edits stay pending so components inserted by callbacks
(e.g. pages content) are restored too. The snapshot is only written when
a hot reload fires and consumed on use, so a manual browser refresh
still resets the app.

Off by default; enable with dev_tools_hot_reload_preserve_state=True or
DASH_HOT_RELOAD_PRESERVE_STATE=true.

…oads

When enabled, the renderer records UI-driven prop edits, set_props
updates (clientside, serverside and websocket) and memory-type dcc.Store
writes as [newVal, originalVal] pairs. Just before a hot reload - soft
RELOAD dispatch or full page reload - they are snapshotted (in memory +
sessionStorage), then re-applied to the incoming layout unless a prop's
initial value changed in the reloaded code, in which case the new code
wins. Unmatched edits stay pending so components inserted by callbacks
(e.g. pages content) are restored too. The snapshot is only written when
a hot reload fires and consumed on use, so a manual browser refresh
still resets the app.

Off by default; enable with dev_tools_hot_reload_preserve_state=True or
DASH_HOT_RELOAD_PRESERVE_STATE=true.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@BSd3v

BSd3v commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

I dont know if this would be worth it or if people would be interested, but could expand this to keep the current state of executed callbacks for seeing in the callback graphs.

@KoolADE85

Copy link
Copy Markdown
Contributor

This is a cool feature, I will find this useful in my own work!
Can we distinguish between "reloading the same app" and "starting a completely different app"?
If you are working on "App A" locally (http://localhost:8050) and you switch to "App B" which happens to share a common callback:

@callback(Input("clear", "n_clicks"))
def clear(n_clicks):
    if not n_clicks:
        raise PreventUpdate

    # does something benign in App A
    # does something destructive in App B

then clicking the button in App A, will cause a problem in App B.
And it wouldn't be obvious that the source of the "bug" came from a button click in App A.

T4rk1n added 2 commits August 27, 2026 13:27
…eload

# Conflicts:
#	CHANGELOG.md
#	dash/dash-renderer/src/observers/executedCallbacks.ts
Switching between two apps served on the same URL could let one app's
preserved UI edits (n_clicks included) be restored into the other, since
the sessionStorage snapshot was keyed only by pathname. Reuse the
per-page-load end_id for this: when preserve_state is on, persist end_id
to disk keyed by the app path (platformdirs with a dependency-free
fallback) so it stays stable across the reload but unique per app, then
scope the sessionStorage snapshot by it. A different app gets a different
end_id and so a different key, and can never consume another app's state.
@sonarqubecloud

Copy link
Copy Markdown

@T4rk1n

T4rk1n commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

@KoolADE85 Good catch, that's a real hazard. I've scoped the preserved state per app: when `preserve_state` is on, the server persists this page's `end_id` to disk keyed by the app path (stable across the reload, unique per app), and the renderer keys its sessionStorage snapshot by it. A different app served on the same URL gets a different `end_id`, so it reads a different key and can never restore another app's state — your App A button click can't reach App B, `n_clicks` included. It reuses the same signed `end_id` token we already mint per page load for background-callback handles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants