Skip to content

Commit 2b09032

Browse files
bug: cancel stale article fetches and add 'unavailable' auth state with retry
Co-Authored-By: patrick.bradley <patrick.bradley@cognition.ai>
1 parent 6de0ac1 commit 2b09032

2 files changed

Lines changed: 42 additions & 11 deletions

File tree

react/src/context/UserContext.tsx

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
2-
import { jwt } from '../api/client';
2+
import { ApiError, jwt } from '../api/client';
33
import { authApi } from '../api/services';
44
import type { User } from '../types';
55

6-
export type AuthState = 'authenticated' | 'unauthenticated' | 'loading';
6+
export type AuthState = 'authenticated' | 'unauthenticated' | 'unavailable' | 'loading';
77

88
interface UserContextValue {
99
user: User | null;
@@ -29,20 +29,37 @@ export function UserProvider({ children }: { children: ReactNode }) {
2929
const [user, setUserState] = useState<User | null>(null);
3030
const [authState, setAuthState] = useState<AuthState>(jwt.getToken() ? 'loading' : 'unauthenticated');
3131

32+
const [retryAttempt, setRetryAttempt] = useState(0);
33+
3234
useEffect(() => {
3335
if (!jwt.getToken()) return;
36+
let cancelled = false;
3437
authApi
3538
.getCurrentUser()
3639
.then(({ user }) => {
40+
if (cancelled) return;
3741
setUserState(user);
3842
setAuthState('authenticated');
3943
})
40-
.catch(() => {
41-
jwt.destroyToken();
42-
setUserState(null);
43-
setAuthState('unauthenticated');
44+
.catch((err: unknown) => {
45+
if (cancelled) return;
46+
if (err instanceof ApiError && err.status >= 400 && err.status < 500) {
47+
jwt.destroyToken();
48+
setUserState(null);
49+
setAuthState('unauthenticated');
50+
} else {
51+
// Server error or network failure: keep the token and retry with backoff
52+
setAuthState('unavailable');
53+
const delay = Math.min(1000 * 2 ** retryAttempt, 30000);
54+
setTimeout(() => {
55+
if (!cancelled) setRetryAttempt(a => a + 1);
56+
}, delay);
57+
}
4458
});
45-
}, []);
59+
return () => {
60+
cancelled = true;
61+
};
62+
}, [retryAttempt]);
4663

4764
const login = useCallback((user: User) => {
4865
jwt.saveToken(user.token);

react/src/pages/Article.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,28 @@ export function Article() {
2222

2323
useEffect(() => {
2424
if (!slug) return;
25+
let cancelled = false;
26+
setArticle(null);
27+
setComments([]);
2528
articlesApi
2629
.get(slug)
27-
.then(({ article }) => setArticle(article))
28-
.catch(() => navigate('/'));
30+
.then(({ article }) => {
31+
if (!cancelled) setArticle(article);
32+
})
33+
.catch(() => {
34+
if (!cancelled) navigate('/');
35+
});
2936
commentsApi
3037
.getAll(slug)
31-
.then(({ comments }) => setComments(comments))
32-
.catch(() => setComments([]));
38+
.then(({ comments }) => {
39+
if (!cancelled) setComments(comments);
40+
})
41+
.catch(() => {
42+
if (!cancelled) setComments([]);
43+
});
44+
return () => {
45+
cancelled = true;
46+
};
3347
}, [slug, navigate]);
3448

3549
const renderedBody = useMemo(() => {

0 commit comments

Comments
 (0)