diff --git a/src/app/(app)/epub/[id]/page.tsx b/src/app/(app)/epub/[id]/page.tsx index d92e358..57f4c0b 100644 --- a/src/app/(app)/epub/[id]/page.tsx +++ b/src/app/(app)/epub/[id]/page.tsx @@ -19,6 +19,7 @@ import { resolveDocumentId } from '@/lib/client/dexie'; import { RateLimitBanner } from '@/components/auth/RateLimitBanner'; import { useAuthRateLimit } from '@/contexts/AuthRateLimitContext'; import { useFeatureFlag } from '@/contexts/RuntimeConfigContext'; +import { useUnmountCleanupRef } from '@/hooks/useUnmountCleanupRef'; import { useEpubDocument } from './useEpubDocument'; export default function EPUBPage() { @@ -46,7 +47,6 @@ export default function EPUBPage() { const inFlightDocIdRef = useRef(null); const loadedDocIdRef = useRef(null); const didInitPadPctRef = useRef(false); - const clearCurrDocRef = useRef(clearCurrDoc); useEffect(() => { setIsLoading(true); @@ -103,15 +103,7 @@ export default function EPUBPage() { loadDocument(); }, [loadDocument, isLoading]); - useEffect(() => { - clearCurrDocRef.current = clearCurrDoc; - }, [clearCurrDoc]); - - useEffect(() => { - return () => { - clearCurrDocRef.current(); - }; - }, []); + useUnmountCleanupRef(clearCurrDoc); // Compute available height = viewport - (header height + tts bar height) useEffect(() => { diff --git a/src/app/(app)/html/[id]/page.tsx b/src/app/(app)/html/[id]/page.tsx index 4272c3b..21ad5c6 100644 --- a/src/app/(app)/html/[id]/page.tsx +++ b/src/app/(app)/html/[id]/page.tsx @@ -17,6 +17,7 @@ import { RateLimitBanner } from '@/components/auth/RateLimitBanner'; import { AudiobookExportModal } from '@/components/AudiobookExportModal'; import { useAuthRateLimit } from '@/contexts/AuthRateLimitContext'; import { useFeatureFlag } from '@/contexts/RuntimeConfigContext'; +import { useUnmountCleanupRef } from '@/hooks/useUnmountCleanupRef'; import type { TTSAudiobookChapter } from '@/types/tts'; import type { AudiobookGenerationSettings } from '@/types/client'; import { useHtmlDocument } from './useHtmlDocument'; @@ -46,7 +47,6 @@ export default function HTMLPage() { const [maxPadPx, setMaxPadPx] = useState(0); const inFlightDocIdRef = useRef(null); const loadedDocIdRef = useRef(null); - const clearCurrDocRef = useRef(clearCurrDoc); useEffect(() => { setIsLoading(true); @@ -103,15 +103,7 @@ export default function HTMLPage() { loadDocument(); }, [loadDocument, isLoading]); - useEffect(() => { - clearCurrDocRef.current = clearCurrDoc; - }, [clearCurrDoc]); - - useEffect(() => { - return () => { - clearCurrDocRef.current(); - }; - }, []); + useUnmountCleanupRef(clearCurrDoc); // Compute available height = viewport - (header height + tts bar height) useEffect(() => { diff --git a/src/app/(app)/pdf/[id]/page.tsx b/src/app/(app)/pdf/[id]/page.tsx index 7413087..7695b4b 100644 --- a/src/app/(app)/pdf/[id]/page.tsx +++ b/src/app/(app)/pdf/[id]/page.tsx @@ -26,6 +26,7 @@ import { FORCE_REPARSE_CONFIRM_TITLE, isForceReparseDisabled, } from '@/lib/client/pdf/force-reparse'; +import { useUnmountCleanupRef } from '@/hooks/useUnmountCleanupRef'; import { usePdfDocument } from './usePdfDocument'; // Dynamic import for client-side rendering only @@ -72,7 +73,6 @@ export default function PDFViewerPage() { const [containerHeight, setContainerHeight] = useState('auto'); const inFlightDocIdRef = useRef(null); const loadedDocIdRef = useRef(null); - const clearCurrDocRef = useRef(clearCurrDoc); const [isNavigatingBack, setIsNavigatingBack] = useState(false); const parseUiState: NonNullable = parseStatus ?? 'pending'; const hasResolvedParseStatus = parseStatus !== null; @@ -153,15 +153,7 @@ export default function PDFViewerPage() { loadDocument(); }, [loadDocument]); - useEffect(() => { - clearCurrDocRef.current = clearCurrDoc; - }, [clearCurrDoc]); - - useEffect(() => { - return () => { - clearCurrDocRef.current(); - }; - }, []); + useUnmountCleanupRef(clearCurrDoc); useEffect(() => { if (isLoading) return; diff --git a/src/hooks/useUnmountCleanupRef.ts b/src/hooks/useUnmountCleanupRef.ts new file mode 100644 index 0000000..ed1a9cb --- /dev/null +++ b/src/hooks/useUnmountCleanupRef.ts @@ -0,0 +1,19 @@ +import { useEffect, useRef } from 'react'; + +/** + * Runs cleanup only on unmount, while always invoking the latest cleanup function. + */ +export function useUnmountCleanupRef(cleanup: () => void) { + const cleanupRef = useRef(cleanup); + + useEffect(() => { + cleanupRef.current = cleanup; + }, [cleanup]); + + useEffect(() => { + return () => { + cleanupRef.current(); + }; + }, []); +} +