Public Access
refactor(hooks): move import polling and settings load into hooks
useImportPolling and useSettingsLoad take the effects out of ImportView and SettingsPage, line for line. useSettingsLoad no longer sets state after unmount. Tests for both hooks. Issue #6.
This commit is contained in:
1 parent
de199a20b7
commit
2b20b389b7
6 files changed
+318
-93
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import {useEffect, useRef, useState} from 'react';
|
||||
import {useState} from 'react';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {toast} from 'sonner';
|
||||
import {CheckCircle2, Circle, Loader2, RotateCcw, XCircle} from 'lucide-react';
|
||||
@@ -8,82 +8,16 @@ import {Card} from '@/components/ui/card';
|
||||
import {cn} from '@/lib/utils';
|
||||
import ImportReview from './ImportReview.jsx';
|
||||
import {useCurrentUserContext} from '../../context/CurrentUserContext.jsx';
|
||||
import {getImport, retryImport, RUNNING, STEPS, stepOf} from '../../utils/imports.js';
|
||||
import useImportPolling from '../../hooks/useImportPolling.js';
|
||||
import {retryImport, RUNNING, STEPS, stepOf} from '../../utils/imports.js';
|
||||
|
||||
const POLL_MS = 2000;
|
||||
const MAX_BACKOFF_MS = 30_000;
|
||||
const MAX_FAILURES = 10;
|
||||
|
||||
// Polls while a worker has the import.
|
||||
export default function ImportView({id, onDone, onChanged}) {
|
||||
const {t} = useTranslation();
|
||||
const [data, setData] = useState(null);
|
||||
const [failed, setFailed] = useState(false);
|
||||
// Grows after a save or a retry: the import is back with a worker, so polling starts again.
|
||||
const [version, setVersion] = useState(0);
|
||||
const reload = () => setVersion((current) => current + 1);
|
||||
const lastStatus = useRef(null);
|
||||
const {data, failed, reload} = useImportPolling(id, onChanged);
|
||||
// undefined while loading: the review preselects with it, so it waits.
|
||||
const {user} = useCurrentUserContext();
|
||||
const favorite = user === undefined ? undefined : user?.settings?.favoritePlatform ?? null;
|
||||
|
||||
useEffect(() => {
|
||||
let timer;
|
||||
let cancelled = false;
|
||||
let failures = 0;
|
||||
let paused = false;
|
||||
// A hidden tab waits for the visibilitychange instead of polling.
|
||||
const poll = () => {
|
||||
if (document.hidden) paused = true;
|
||||
else load();
|
||||
};
|
||||
const onVisible = () => {
|
||||
if (!document.hidden && paused) {
|
||||
paused = false;
|
||||
load();
|
||||
}
|
||||
};
|
||||
// A 5xx while Core restarts or a network error is asked again, each time twice as late.
|
||||
const retry = () => {
|
||||
if (++failures >= MAX_FAILURES) setFailed(true);
|
||||
else timer = setTimeout(poll, Math.min(POLL_MS * 2 ** failures, MAX_BACKOFF_MS));
|
||||
};
|
||||
const load = async () => {
|
||||
try {
|
||||
const response = await getImport(id);
|
||||
if (cancelled) return;
|
||||
if (response.status === 401) return;
|
||||
// A 4xx ends the polling.
|
||||
if (response.status >= 400 && response.status < 500) {
|
||||
setFailed(true);
|
||||
return;
|
||||
}
|
||||
if (!response.ok) {
|
||||
retry();
|
||||
return;
|
||||
}
|
||||
const next = await response.json();
|
||||
if (cancelled) return;
|
||||
failures = 0;
|
||||
// A new status also shows in the history.
|
||||
if (lastStatus.current && lastStatus.current !== next.status) onChanged();
|
||||
lastStatus.current = next.status;
|
||||
setData(next);
|
||||
if (RUNNING.includes(next.status)) timer = setTimeout(poll, POLL_MS);
|
||||
} catch (err) {
|
||||
console.error('Import load error:', err);
|
||||
if (!cancelled) retry();
|
||||
}
|
||||
};
|
||||
document.addEventListener('visibilitychange', onVisible);
|
||||
load();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
clearTimeout(timer);
|
||||
document.removeEventListener('visibilitychange', onVisible);
|
||||
};
|
||||
}, [id, version, onChanged]);
|
||||
|
||||
if (failed) return <p className="text-sm text-destructive">{t('IMPORT.LOAD_FAILED')}</p>;
|
||||
if (!data || favorite === undefined) return <Loader2 className="animate-spin text-muted-foreground"/>;
|
||||
return (
|
||||
|
||||
Reference in new issue
Block a user