修复我的收藏/查找聊天记录加载失败时卡死在"加载中…",没有任何错误提示或重试入口
根因:Favorites 和 History(content-library-view.jsx)两个组件的 load() 里,请求失败的 .catch() 只调用 onToast() 弹一条 2 秒自动消失的提示,从未把 items 从初始值 null 变成别的 状态;页面渲染逻辑是"items === null 就显示'加载中…'",两者叠加导致一次真实失败之后页面永远 停在"加载中…",等用户反应过来去看的时候提示早就消失了,分不清"还在加载"和"永远不会加载完"。 这个坑本文件自己的 DownloadPage、group-management-view.jsx 的 GroupFiles、 contact-management-view.jsx 的 Profile 三处早就各自修过(都有一个独立的 loadError 状态), 唯独 Favorites/History 这两个用得最多的列表页当时漏掉了。 真实触发场景:用两个全新账号互加好友、发送图片/视频/文件/语音/文本等真实消息并收藏后, 发现收藏一条较大的(out-of-band)文件附件之后,GET /favorites?type=all 从此稳定返回 500 INTERNAL_ERROR(已确认是纯后端问题,不在前端能修的范围内,另行记录)——但这个后端 问题一暴露出来,"我的收藏"默认落地的"全部"tab 就会用真实数据 100% 复现这里描述的前端缺口: 打开就卡在"加载中…",没有任何办法知道发生了什么、也无法重试。 修复:Favorites/History 都新增 loadError 状态,非 append(首次/切换 tab、类型、搜索词)请求 失败时置为 true、成功时置回 false,切换 type/topic/view/query 时重置;渲染时 items === null 且 loadError 为真则显示"加载失败,请重试"而不是死等的"加载中…"。"加载更多"(append)请求 失败保持原样只弹 toast,不影响已经显示出来的列表。 验证:复现上述 500 场景(GET /favorites?type=all 持续 500),修复前"我的收藏"全部 tab 永久停在"加载中…";重新构建后同一账号同一坏数据状态下正确显示"加载失败,请重试"。
This commit is contained in:
@@ -290,6 +290,16 @@ function Favorites({view, hashParams, tinode, onToast}) {
|
||||
const [items, setItems] = React.useState(() => readCachedData('favorites:' + type) || null);
|
||||
const [cursor, setCursor] = React.useState('');
|
||||
const [hasMore, setHasMore] = React.useState(false);
|
||||
// A failed initial load (no cached list to fall back on) previously left this stuck on "加载中…"
|
||||
// forever -- the catch handler only fired a toast, which fades in 2s, and `items` never left null
|
||||
// so the loading branch below never had anything else to show. Same fix already applied to this
|
||||
// file's own DownloadPage/GroupFiles(group-management-view.jsx)/Profile(contact-management-view.jsx)
|
||||
// for the identical reason; Favorites/History were missed. Concretely hit live: favoriting a large
|
||||
// (out-of-band) file attachment makes the *server* 500 on GET /favorites?type=all from then on
|
||||
// (a real backend bug, tracked separately -- not something the frontend can prevent), and every
|
||||
// subsequent visit to "全部" silently hung on the loading spinner with only a 2s toast, no way to
|
||||
// tell "still loading" from "never going to load", no retry affordance.
|
||||
const [loadError, setLoadError] = React.useState(false);
|
||||
const [fileUrls, setFileUrls] = React.useState({});
|
||||
const [sourceNames, setSourceNames] = React.useState({});
|
||||
// An in-flight request isn't guaranteed to resolve before a newer one its own type/query change
|
||||
@@ -302,6 +312,7 @@ function Favorites({view, hashParams, tinode, onToast}) {
|
||||
const requestId = ++requestIdRef.current;
|
||||
getFavorites({type, query, cursor: append ? cursor : ''}).then(data => {
|
||||
if (requestIdRef.current !== requestId) return;
|
||||
setLoadError(false);
|
||||
setItems(prev => {
|
||||
const list = append ? (prev || []).concat(data.items || []) : (data.items || []);
|
||||
if (!query) writeCachedData('favorites:' + type, list);
|
||||
@@ -309,9 +320,14 @@ function Favorites({view, hashParams, tinode, onToast}) {
|
||||
});
|
||||
setCursor(data.next_cursor || '');
|
||||
setHasMore(!!data.next_cursor);
|
||||
}).catch(err => onToast(err.message || '加载失败'));
|
||||
}).catch(err => {
|
||||
if (requestIdRef.current !== requestId) return;
|
||||
onToast(err.message || '加载失败');
|
||||
if (!append) setLoadError(true);
|
||||
});
|
||||
}, [type, query, cursor]);
|
||||
React.useEffect(() => {
|
||||
setLoadError(false);
|
||||
setItems((!query && readCachedData('favorites:' + type)) || null);
|
||||
load(false);
|
||||
}, [type, query]);
|
||||
@@ -380,7 +396,7 @@ function Favorites({view, hashParams, tinode, onToast}) {
|
||||
// 类型列表内的勾选,没有跨 tab 累加的设计)。
|
||||
onClick={() => { setType(id); setSelected([]); if (TYPE_ROUTE[id] && !selecting) navigateReplace(TYPE_ROUTE[id]); }}>{label}</button>)}</nav>
|
||||
<main className="library-favorite-list">
|
||||
{items === null ? <p className="library-empty-hint">加载中…</p> :
|
||||
{items === null ? <p className="library-empty-hint">{loadError ? '加载失败,请重试' : '加载中…'}</p> :
|
||||
list.length ? <>{list.map(item => <FavoriteItem key={item.favorite_id} item={{...item, _thumbUrl: fileUrls[item.favorite_id],
|
||||
source_display: sourceNames[item.source && item.source.topic]}}
|
||||
selecting={selecting} selected={selected.includes(item.favorite_id)} onSelect={() => toggle(item.favorite_id)}
|
||||
@@ -614,6 +630,10 @@ function History({view, hashParams, myUserId, onToast}) {
|
||||
const [items, setItems] = React.useState(() => (cacheKey && readCachedData(cacheKey)) || null);
|
||||
const [cursor, setCursor] = React.useState('');
|
||||
const [hasMore, setHasMore] = React.useState(false);
|
||||
// Same "stuck on 加载中… forever" gap as Favorites' loadError above, same fix -- a failed initial
|
||||
// load with nothing cached to fall back on left this on the loading branch permanently, with only
|
||||
// a 2s toast (already faded by the time anyone looks back) as the only sign anything went wrong.
|
||||
const [loadError, setLoadError] = React.useState(false);
|
||||
const [mediaUrls, setMediaUrls] = React.useState({});
|
||||
const [senderNames, setSenderNames] = React.useState({});
|
||||
// Same stale-response guard as Favorites' load() above and for the same reason -- a slower
|
||||
@@ -628,15 +648,21 @@ function History({view, hashParams, myUserId, onToast}) {
|
||||
getTopicMedia(topic, type, append ? cursor : '');
|
||||
call.then(data => {
|
||||
if (requestIdRef.current !== requestId) return;
|
||||
setLoadError(false);
|
||||
setItems(prev => {
|
||||
const list = append ? (prev || []).concat(data.items || []) : (data.items || []);
|
||||
if (!query && cacheKey) writeCachedData(cacheKey, list);
|
||||
return list;
|
||||
});
|
||||
setCursor(data.next_cursor || ''); setHasMore(!!data.next_cursor);
|
||||
}).catch(err => onToast(err.message || '加载失败'));
|
||||
}).catch(err => {
|
||||
if (requestIdRef.current !== requestId) return;
|
||||
onToast(err.message || '加载失败');
|
||||
if (!append) setLoadError(true);
|
||||
});
|
||||
}, [topic, view, query, cursor]);
|
||||
React.useEffect(() => {
|
||||
setLoadError(false);
|
||||
setItems((!query && cacheKey && readCachedData(cacheKey)) || null);
|
||||
load(false);
|
||||
}, [topic, view, query]);
|
||||
@@ -687,7 +713,7 @@ function History({view, hashParams, myUserId, onToast}) {
|
||||
{view === 'history-search' ? <Search value={queryInput} onChange={setQueryInput} placeholder="搜索当前会话" /> : null}
|
||||
<HistoryNav active={view} />
|
||||
<main className="library-history-content">
|
||||
{items === null ? <p className="library-empty-hint">加载中…</p> : !list.length ?
|
||||
{items === null ? <p className="library-empty-hint">{loadError ? '加载失败,请重试' : '加载中…'}</p> : !list.length ?
|
||||
<div className="library-empty"><Icon>{view === 'history-search' ? 'search_off' : 'inbox'}</Icon><strong>暂无内容</strong></div> :
|
||||
view === 'history-search' ? <section className="library-message-results">
|
||||
{/* 同一处"问题13"修复暴露的缺口,History 自己的一份,只影响 type==='text':非 text 类型
|
||||
|
||||
Reference in New Issue
Block a user