refactor: enhance content loading and cancellation with AbortController and improved state reset logic.

This commit is contained in:
2025-11-29 14:27:35 +01:00
parent 79aa359c1f
commit 9cf63ece72
+21 -14
View File
@@ -333,13 +333,16 @@ const DocumentInfoPanel: React.FC<DocumentInfoPanelProps> = ({
// Track previous ID/key to avoid unnecessary resets on prop reference changes // Track previous ID/key to avoid unnecessary resets on prop reference changes
const prevDocIdRef = React.useRef(document?.id); const prevDocIdRef = React.useRef(document?.id);
const prevResetKeyRef = React.useRef(resetKey); const prevResetKeyRef = React.useRef(resetKey);
const activeControllerRef = React.useRef<AbortController | null>(null);
// Reset content state when document changes // Reset content state when document changes or contentConfig becomes available
useEffect(() => { useEffect(() => {
const docIdChanged = prevDocIdRef.current !== document?.id; const docIdChanged = prevDocIdRef.current !== document?.id;
const resetKeyChanged = prevResetKeyRef.current !== resetKey; 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; prevDocIdRef.current = document?.id;
prevResetKeyRef.current = resetKey; prevResetKeyRef.current = resetKey;
@@ -361,29 +364,38 @@ const DocumentInfoPanel: React.FC<DocumentInfoPanelProps> = ({
loadContent, loadContent,
document?.id, document?.id,
resetKey, 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(() => { useEffect(() => {
const contentTabId = contentConfig?.id || 'content'; const contentTabId = contentConfig?.id || 'content';
const isActive = activeTabId === contentTabId; const isActive = activeTabId === contentTabId;
if (!isActive || !contentConfig || !loadContent || !contentEnabled) { if (!isActive || !contentConfig || !loadContent || !contentEnabled) {
return undefined; return;
} }
if (contentState?.status !== 'idle') { if (contentState?.status !== 'idle') {
return undefined; return;
} }
let cancelled = false;
const controller = new AbortController(); const controller = new AbortController();
activeControllerRef.current = controller;
setContentState({ status: 'loading', data: null, error: null }); setContentState({ status: 'loading', data: null, error: null });
Promise.resolve(loadContent({ signal: controller.signal })) Promise.resolve(loadContent({ signal: controller.signal }))
.then((result) => { .then((result) => {
if (cancelled) { if (controller.signal.aborted) {
return; return;
} }
if (result && result.length) { if (result && result.length) {
@@ -393,7 +405,7 @@ const DocumentInfoPanel: React.FC<DocumentInfoPanelProps> = ({
} }
}) })
.catch((error) => { .catch((error) => {
if (cancelled || error?.name === 'AbortError') { if (controller.signal.aborted || error?.name === 'AbortError') {
return; return;
} }
setContentState({ setContentState({
@@ -402,18 +414,13 @@ const DocumentInfoPanel: React.FC<DocumentInfoPanelProps> = ({
error, error,
}); });
}); });
return () => {
cancelled = true;
controller.abort();
contentConfig.onCancel?.();
};
}, [ }, [
activeTabId, activeTabId,
contentConfig, contentConfig,
loadContent, loadContent,
contentEnabled, contentEnabled,
contentState?.status, contentState?.status,
document?.id,
]); ]);
const handleTabSelect = (tabId) => { const handleTabSelect = (tabId) => {