From 94f823cc02dba6eb4f29e9b86b4e11a63e3f71aa Mon Sep 17 00:00:00 2001 From: Suryakant Sahoo <230859682+suryakant-source@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:52:00 +0530 Subject: [PATCH 1/3] fix: sync localStorage clear events across tabs --- .../reflex_base/.templates/web/utils/state.js | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/reflex-base/src/reflex_base/.templates/web/utils/state.js b/packages/reflex-base/src/reflex_base/.templates/web/utils/state.js index aa79aedf662..012ef02bc09 100644 --- a/packages/reflex-base/src/reflex_base/.templates/web/utils/state.js +++ b/packages/reflex-base/src/reflex_base/.templates/web/utils/state.js @@ -1610,14 +1610,24 @@ export const useEventLoop = ( // e is StorageEvent const handleStorage = (e) => { - const state_keys = storage_to_state_map.get(e.key); // Session storage changes in same-origin frames raise storage events too. - if (state_keys && e.storageArea === localStorage) { - // The value is read when the event is sent: see syncLocalStorageEvent. - addEvents( - [ReflexEvent(SYNC_LOCAL_STORAGE, { key: e.key, state_keys })], - e, - ); + if (e.storageArea !== localStorage) { + return; + } + // A clear() event has no key and affects every synced storage name. + const entries = + e.key === null + ? storage_to_state_map.entries() + : [[e.key, storage_to_state_map.get(e.key)]]; + const events = []; + for (const [key, state_keys] of entries) { + if (state_keys) { + // Read the value when sent, just like individual-key updates. + events.push(ReflexEvent(SYNC_LOCAL_STORAGE, { key, state_keys })); + } + } + if (events.length) { + addEvents(events, e); } }; From 5b6ab04d92cef7eaf67745a74b50071bbf555328 Mon Sep 17 00:00:00 2001 From: Suryakant Sahoo <230859682+suryakant-source@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:54:57 +0530 Subject: [PATCH 2/3] test: cover localStorage and sessionStorage clear events --- tests/units/compiler/state_js.test.mjs | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/units/compiler/state_js.test.mjs b/tests/units/compiler/state_js.test.mjs index 688814a2fde..28fa6e5caa5 100644 --- a/tests/units/compiler/state_js.test.mjs +++ b/tests/units/compiler/state_js.test.mjs @@ -724,3 +724,57 @@ test("storage names that are Object.prototype properties sync like any other", a JSON.stringify({ third: null }), ); }); + +test("clearing localStorage syncs every configured name but not unsynced vars", async () => { + const { harness, effects, sent } = await mountEventLoop({ + local_storage: { + first: { name: "shared", sync: true }, + second: { name: "shared", sync: true }, + other: { sync: true }, + unsynced: { name: "shared" }, + }, + }); + effects.forEach((effect) => effect()); + const event = new Event("storage"); + event.key = null; + event.storageArea = harness.localStorage; + harness.window.dispatchEvent(event); + await new Promise((resolve) => setImmediate(resolve)); + const updates = sent.filter((event) => + event.name.endsWith("update_vars_internal"), + ); + assert.equal(updates.length, 2, JSON.stringify(sent)); + assert.equal( + JSON.stringify(updates[0].payload.vars), + JSON.stringify({ first: null, second: null }), + ); + assert.equal( + JSON.stringify(updates[1].payload.vars), + JSON.stringify({ other: null }), + ); +}); + +test("a sessionStorage clear does not sync localStorage vars", async () => { + const { harness, effects, sent } = await mountEventLoop({ + local_storage: { first: { sync: true } }, + }); + effects.forEach((effect) => effect()); + const event = new Event("storage"); + event.key = null; + event.storageArea = harness.window.sessionStorage; + harness.window.dispatchEvent(event); + assert.equal(sent.length, 0); +}); + +test("clearing localStorage without synced vars queues no updates", async () => { + const { harness, effects, sent } = await mountEventLoop({ + local_storage: { unsynced: { name: "unused" } }, + }); + for (const effect of effects) effect(); + const event = new Event("storage"); + event.key = null; + event.storageArea = harness.localStorage; + harness.window.dispatchEvent(event); + await new Promise((resolve) => setImmediate(resolve)); + assert.equal(sent.length, 0); +}); From 888dde85f08b613de781196ed36fd52bf4255f90 Mon Sep 17 00:00:00 2001 From: Suryakant Sahoo <230859682+suryakant-source@users.noreply.github.com> Date: Sat, 24 Oct 2026 08:07:26 +0530 Subject: [PATCH 3/3] Create +sync-local-storage-clear.bugfix.md --- packages/reflex-base/news/+sync-local-storage-clear.bugfix.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/reflex-base/news/+sync-local-storage-clear.bugfix.md diff --git a/packages/reflex-base/news/+sync-local-storage-clear.bugfix.md b/packages/reflex-base/news/+sync-local-storage-clear.bugfix.md new file mode 100644 index 00000000000..1d6c12cc880 --- /dev/null +++ b/packages/reflex-base/news/+sync-local-storage-clear.bugfix.md @@ -0,0 +1 @@ +Synchronize configured local storage state across tabs when localStorage is cleared.