From be7e79be5504975fe65266d710cfc6a91dcd1b45 Mon Sep 17 00:00:00 2001 From: Ludy Date: Mon, 6 Oct 2025 13:24:12 +0200 Subject: [PATCH] fix(viewer): make initial zoom setup robust and clear timers in `ZoomAPIBridge` (#4588) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description of Changes **What was changed** - Reworked the initial zoom `useEffect`: - Early return when `zoom` is unavailable or initial zoom was already applied. - Extracted an `attemptInitialZoom` function with a single retry path. - Introduced proper timeout handles (`timer`, `retryTimer`) and added a cleanup function to clear them on unmount/re-render. - Expanded the effect dependency array to include `zoomState` to avoid stale state issues. - Switched to nullish coalescing for safer defaulting of `currentZoomLevel` (`zoomState.currentZoomLevel ?? 1.4`). - Minor logging adjustments to clarify delayed/failed initialization paths. **Why the change was made** - The previous implementation risked leaving dangling timers and could re-attempt zoom unnecessarily, causing flicker or inconsistent initial zoom when the viewport wasn’t ready. - Including `zoomState` in dependencies ensures the component reacts to state changes correctly and avoids stale reads. - Cleanup of timers prevents memory leaks and race conditions during rapid mounts/unmounts or navigation. --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com> --- .../src/components/viewer/ZoomAPIBridge.tsx | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/viewer/ZoomAPIBridge.tsx b/frontend/src/components/viewer/ZoomAPIBridge.tsx index fa47b1c8a..129cf00bd 100644 --- a/frontend/src/components/viewer/ZoomAPIBridge.tsx +++ b/frontend/src/components/viewer/ZoomAPIBridge.tsx @@ -12,30 +12,42 @@ export function ZoomAPIBridge() { // Set initial zoom once when plugin is ready useEffect(() => { - if (zoom && !hasSetInitialZoom.current) { - hasSetInitialZoom.current = true; - setTimeout(() => { - try { - zoom.requestZoom(1.4); - } catch (error) { - console.log('Zoom initialization delayed, viewport not ready:', error); - // Retry after a longer delay - setTimeout(() => { - try { - zoom.requestZoom(1.4); - } catch (retryError) { - console.log('Zoom initialization failed:', retryError); - } - }, 200); - } - }, 50); + if (!zoom || hasSetInitialZoom.current) { + return; } - }, [zoom]); + + let retryTimer: ReturnType | undefined; + const attemptInitialZoom = () => { + try { + zoom.requestZoom(1.4); + hasSetInitialZoom.current = true; + } catch (error) { + console.log('Zoom initialization delayed, viewport not ready:', error); + retryTimer = setTimeout(() => { + try { + zoom.requestZoom(1.4); + hasSetInitialZoom.current = true; + } catch (retryError) { + console.log('Zoom initialization failed:', retryError); + } + }, 200); + } + }; + + const timer = setTimeout(attemptInitialZoom, 50); + + return () => { + clearTimeout(timer); + if (retryTimer) { + clearTimeout(retryTimer); + } + }; + }, [zoom, zoomState]); useEffect(() => { if (zoom && zoomState) { // Update local state - const currentZoomLevel = zoomState.currentZoomLevel || 1.4; + const currentZoomLevel = zoomState.currentZoomLevel ?? 1.4; const newState = { currentZoom: currentZoomLevel, zoomPercent: Math.round(currentZoomLevel * 100),