authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-09 12:28:26-07:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-09 14:02:29-07:00
logf5e4df5cab247021501876358fd72c7196cd96b9
treeff56ab6b3b3353eb1811356510407686849c2c47
parent1f47cc6f4e51d236421d45bafabaa3bbf2576c3f

Use Debouncer for store state persistence

- Replace manual timeout debounce logic with `@tanstack/react-pacer`'s `Debouncer` - Persist updates via `maybeExecute` to reduce localStorage write thrashing - Flush pending persistence on `beforeunload` to avoid losing recent state

1 files changed, 4 insertions(+), 18 deletions(-)

apps/web/src/store.ts+4-18
...@@ -14,6 +14,7 @@ import {...@@ -14,6 +14,7 @@ import {
14} from "@t3tools/shared/model";14} from "@t3tools/shared/model";
15import { create } from "zustand";15import { create } from "zustand";
16import { type ChatMessage, type Project, type Thread } from "./types";16import { type ChatMessage, type Project, type Thread } from "./types";
17import { Debouncer } from "@tanstack/react-pacer";
1718
18// ── State ────────────────────────────────────────────────────────────19// ── State ────────────────────────────────────────────────────────────
1920
...@@ -86,18 +87,7 @@ function persistState(state: AppState): void {...@@ -86,18 +87,7 @@ function persistState(state: AppState): void {
86 // Ignore quota/storage errors to avoid breaking chat UX.87 // Ignore quota/storage errors to avoid breaking chat UX.
87 }88 }
88}89}
8990const debouncedPersistState = new Debouncer(persistState, { wait: 500 });
90let persistTimer: ReturnType<typeof setTimeout> | null = null;
91
92function debouncedPersistState(state: AppState): void {
93 if (persistTimer !== null) {
94 clearTimeout(persistTimer);
95 }
96 persistTimer = setTimeout(() => {
97 persistTimer = null;
98 persistState(state);
99 }, 500);
100}
10191
102// ── Pure helpers ──────────────────────────────────────────────────────92// ── Pure helpers ──────────────────────────────────────────────────────
10393
...@@ -413,16 +403,12 @@ export const useStore = create<AppStore>((set) => ({...@@ -413,16 +403,12 @@ export const useStore = create<AppStore>((set) => ({
413}));403}));
414404
415// Persist state changes with debouncing to avoid localStorage thrashing405// Persist state changes with debouncing to avoid localStorage thrashing
416useStore.subscribe((state) => debouncedPersistState(state));406useStore.subscribe((state) => debouncedPersistState.maybeExecute(state));
417407
418// Flush pending writes synchronously before page unload to prevent data loss.408// Flush pending writes synchronously before page unload to prevent data loss.
419if (typeof window !== "undefined") {409if (typeof window !== "undefined") {
420 window.addEventListener("beforeunload", () => {410 window.addEventListener("beforeunload", () => {
421 if (persistTimer !== null) {411 debouncedPersistState.flush();
422 clearTimeout(persistTimer);
423 persistTimer = null;
424 persistState(useStore.getState());
425 }
426 });412 });
427}413}
428414