From 14f141babf611222a0aef77e38fb3b7381302633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arn=C3=BE=C3=B3r=20Sn=C3=A6r=20S=C3=A6varsson?= Date: Wed, 29 Apr 2026 13:54:23 +0000 Subject: [PATCH] fix(examples/ts-code-mode-web): persist reports across reloads The debounced save in usePersistedReports was created once and cached in a ref, capturing the initial empty 'reports' Map and null 'activeReportId' from the first effect run. Subsequent renders re-fired the effect but reused the cached closure, so every localStorage.setItem wrote the empty initial state. Recreate the debounced save each effect run; the cleanup cancels the in-flight debounce on each render so debouncing semantics are preserved. Repro: 1. Run examples/ts-code-mode-web (pnpm dev) and open /reporting-agent 2. Generate a report 3. DevTools > Application > Local Storage > 'tanstack-ai-reports' shows {"reports":[],"activeReportId":null,"version":1} despite the populated UI; reload makes the report disappear --- .../src/lib/reports/use-persisted-reports.ts | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/examples/ts-code-mode-web/src/lib/reports/use-persisted-reports.ts b/examples/ts-code-mode-web/src/lib/reports/use-persisted-reports.ts index 828c95905..3611f6f27 100644 --- a/examples/ts-code-mode-web/src/lib/reports/use-persisted-reports.ts +++ b/examples/ts-code-mode-web/src/lib/reports/use-persisted-reports.ts @@ -1,6 +1,6 @@ 'use client' -import { useState, useEffect, useCallback, useRef } from 'react' +import { useState, useEffect, useCallback } from 'react' import type { Report, ReportState, UIEvent, UINode } from './types' import { applyUIEvent, createEmptyReportState } from './apply-event' @@ -42,7 +42,6 @@ export function usePersistedReports() { const [reports, setReports] = useState>(new Map()) const [activeReportId, setActiveReportId] = useState(null) const [isHydrated, setIsHydrated] = useState(false) - const saveRef = useRef void>> | null>(null) // Load from localStorage on mount useEffect(() => { @@ -74,29 +73,27 @@ export function usePersistedReports() { useEffect(() => { if (!isHydrated) return - if (!saveRef.current) { - saveRef.current = debounce(() => { - const data: ReportsStorage = { - reports: Array.from(reports.values()).map( - ({ report, nodes, rootIds }) => ({ - report, - nodes: Array.from(nodes.entries()), - rootIds, - }), - ), - activeReportId, - version: STORAGE_VERSION, - } - try { - localStorage.setItem(STORAGE_KEY, JSON.stringify(data)) - } catch (e) { - console.error('Failed to save reports to storage:', e) - } - }, 500) - } + const save = debounce(() => { + const data: ReportsStorage = { + reports: Array.from(reports.values()).map( + ({ report, nodes, rootIds }) => ({ + report, + nodes: Array.from(nodes.entries()), + rootIds, + }), + ), + activeReportId, + version: STORAGE_VERSION, + } + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(data)) + } catch (e) { + console.error('Failed to save reports to storage:', e) + } + }, 500) - saveRef.current() - return () => saveRef.current?.cancel() + save() + return () => save.cancel() }, [reports, activeReportId, isHydrated]) // Create a new report