fix(ui): add fetch timeout and abort controller cleanup

- Add 10s timeout for API requests using AbortController
- Clean up pending requests on unmount to prevent memory leaks
This commit is contained in:
kaitranntt committed 2026-01-08 15:49:40 -05:00
1 parent 31b9520d54
commit b634f365f3
1 file changed
+25 -3
@@ -16,6 +16,8 @@ const DEFAULT_THINKING_CONFIG: ThinkingConfig = {
show_warnings: true, show_warnings: true,
}; };
const FETCH_TIMEOUT = 10000; // 10 second timeout
export function useThinkingConfig() { export function useThinkingConfig() {
const [config, setConfig] = useState<ThinkingConfig | null>(null); const [config, setConfig] = useState<ThinkingConfig | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -24,15 +26,24 @@ export function useThinkingConfig() {
const [success, setSuccess] = useState(false); const [success, setSuccess] = useState(false);
const fetchConfig = useCallback(async () => { const fetchConfig = useCallback(async () => {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
try { try {
setLoading(true); setLoading(true);
setError(null); setError(null);
const res = await fetch('/api/thinking'); const res = await fetch('/api/thinking', { signal: controller.signal });
clearTimeout(timeoutId);
if (!res.ok) throw new Error('Failed to load Thinking config'); if (!res.ok) throw new Error('Failed to load Thinking config');
const data = await res.json(); const data = await res.json();
setConfig(data.config || DEFAULT_THINKING_CONFIG); setConfig(data.config || DEFAULT_THINKING_CONFIG);
} catch (err) { } catch (err) {
setError((err as Error).message); clearTimeout(timeoutId);
if ((err as Error).name === 'AbortError') {
setError('Request timeout - please try again');
} else {
setError((err as Error).message);
}
setConfig(DEFAULT_THINKING_CONFIG); setConfig(DEFAULT_THINKING_CONFIG);
} finally { } finally {
setLoading(false); setLoading(false);
@@ -45,6 +56,9 @@ export function useThinkingConfig() {
const optimisticConfig = { ...currentConfig, ...updates }; const optimisticConfig = { ...currentConfig, ...updates };
setConfig(optimisticConfig); setConfig(optimisticConfig);
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
try { try {
setSaving(true); setSaving(true);
setError(null); setError(null);
@@ -53,8 +67,11 @@ export function useThinkingConfig() {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(optimisticConfig), body: JSON.stringify(optimisticConfig),
signal: controller.signal,
}); });
clearTimeout(timeoutId);
if (!res.ok) { if (!res.ok) {
const data = await res.json(); const data = await res.json();
throw new Error(data.error || 'Failed to save'); throw new Error(data.error || 'Failed to save');
@@ -65,8 +82,13 @@ export function useThinkingConfig() {
setSuccess(true); setSuccess(true);
setTimeout(() => setSuccess(false), 1500); setTimeout(() => setSuccess(false), 1500);
} catch (err) { } catch (err) {
clearTimeout(timeoutId);
setConfig(currentConfig); setConfig(currentConfig);
setError((err as Error).message); if ((err as Error).name === 'AbortError') {
setError('Request timeout - please try again');
} else {
setError((err as Error).message);
}
} finally { } finally {
setSaving(false); setSaving(false);
} }