diff --git a/frontend/src/documents/DocumentInfoPanel.tsx b/frontend/src/documents/DocumentInfoPanel.tsx index 7d01e64..02f50a4 100644 --- a/frontend/src/documents/DocumentInfoPanel.tsx +++ b/frontend/src/documents/DocumentInfoPanel.tsx @@ -333,13 +333,16 @@ const DocumentInfoPanel: React.FC = ({ // Track previous ID/key to avoid unnecessary resets on prop reference changes const prevDocIdRef = React.useRef(document?.id); const prevResetKeyRef = React.useRef(resetKey); + const activeControllerRef = React.useRef(null); - // Reset content state when document changes + // Reset content state when document changes or contentConfig becomes available useEffect(() => { const docIdChanged = prevDocIdRef.current !== document?.id; const resetKeyChanged = prevResetKeyRef.current !== resetKey; + // Check if we need to initialize state (e.g. contentConfig was loaded asynchronously) + const needsInit = contentConfig && !contentState; - if (docIdChanged || resetKeyChanged) { + if (docIdChanged || resetKeyChanged || needsInit) { prevDocIdRef.current = document?.id; prevResetKeyRef.current = resetKey; @@ -361,29 +364,38 @@ const DocumentInfoPanel: React.FC = ({ loadContent, document?.id, resetKey, + contentState, ]); - // Lazy load content when tab is active + // Cleanup effect: aborts when inputs change or component unmounts + useEffect(() => { + return () => { + activeControllerRef.current?.abort(); + contentConfig?.onCancel?.(); + }; + }, [activeTabId, contentConfig, loadContent, contentEnabled, document?.id]); + + // Loading effect: triggers load when status is idle useEffect(() => { const contentTabId = contentConfig?.id || 'content'; const isActive = activeTabId === contentTabId; if (!isActive || !contentConfig || !loadContent || !contentEnabled) { - return undefined; + return; } if (contentState?.status !== 'idle') { - return undefined; + return; } - let cancelled = false; const controller = new AbortController(); + activeControllerRef.current = controller; setContentState({ status: 'loading', data: null, error: null }); Promise.resolve(loadContent({ signal: controller.signal })) .then((result) => { - if (cancelled) { + if (controller.signal.aborted) { return; } if (result && result.length) { @@ -393,7 +405,7 @@ const DocumentInfoPanel: React.FC = ({ } }) .catch((error) => { - if (cancelled || error?.name === 'AbortError') { + if (controller.signal.aborted || error?.name === 'AbortError') { return; } setContentState({ @@ -402,18 +414,13 @@ const DocumentInfoPanel: React.FC = ({ error, }); }); - - return () => { - cancelled = true; - controller.abort(); - contentConfig.onCancel?.(); - }; }, [ activeTabId, contentConfig, loadContent, contentEnabled, contentState?.status, + document?.id, ]); const handleTabSelect = (tabId) => {