Topics: Fix loading after refresh (#6967)

This commit is contained in:
zubiden 2026-06-01 01:16:04 +02:00 committed by Alexander Zinchuk
parent 73b5ebeeba
commit d7082efd93
6 changed files with 54 additions and 24 deletions

View File

@ -17,7 +17,7 @@ import {
selectMonoforumChannel,
selectPeer,
selectTabState,
selectTopics,
selectTopicsInfo,
selectUserStatus,
} from '../../../global/selectors';
import { selectAnimationLevel } from '../../../global/selectors/sharedState';
@ -135,15 +135,16 @@ const ChatOrUserPicker = ({
useInputFocusOnOpen(searchRef, isOpen && activeKey === CHAT_LIST_SLIDE, resetSearch);
useInputFocusOnOpen(topicSearchRef, isOpen && activeKey === TOPIC_LIST_SLIDE);
const selectTopicsById = useCallback((global: GlobalState) => {
const selectForumTopicsInfo = useCallback((global: GlobalState) => {
if (!forumId) {
return undefined;
}
return selectTopics(global, forumId);
return selectTopicsInfo(global, forumId);
}, [forumId]);
const forumTopicsById = useSelector(selectTopicsById);
const topicsInfo = useSelector(selectForumTopicsInfo);
const forumTopicsById = topicsInfo?.topicsById;
const [topicIds, topics] = useMemo(() => {
const global = getGlobal();
@ -194,7 +195,7 @@ const ChatOrUserPicker = ({
const chatId = viewportIds[index === -1 ? 0 : index];
const chat = chatsById[chatId];
if (chat?.isForum) {
if (!forumTopicsById) loadTopics({ chatId });
if (!topicsInfo || topicsInfo.isCache) loadTopics({ chatId });
setForumId(chatId);
} else {
onSelectChatOrUser(chatId);
@ -218,7 +219,7 @@ const ChatOrUserPicker = ({
const chatsById = getGlobal().chats.byId;
const chat = chatsById?.[chatId];
if (chat?.isForum) {
if (!forumTopicsById) loadTopics({ chatId });
if (!topicsInfo || topicsInfo.isCache) loadTopics({ chatId });
setForumId(chatId);
resetSearch();
} else {

View File

@ -18,7 +18,7 @@ import type {
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import type { ChatAnimationTypes } from './hooks';
import { MAIN_THREAD_ID } from '../../../api/types';
import { StoryViewerOrigin } from '../../../types';
import { StoryViewerOrigin, type TopicsInfo } from '../../../types';
import { ALL_FOLDER_ID, UNMUTE_TIMESTAMP } from '../../../config';
import {
@ -106,7 +106,7 @@ type StateProps = {
chat?: ApiChat;
monoforumChannel?: ApiChat;
lastMessageStory?: ApiTypeStory;
listedTopicIds?: number[];
topicsInfo?: TopicsInfo;
isMuted?: boolean;
user?: ApiUser;
userStatus?: ApiUserStatus;
@ -139,7 +139,7 @@ const Chat: FC<OwnProps & StateProps> = ({
shiftDiff,
animationType,
isPinned,
listedTopicIds,
topicsInfo,
observeIntersection,
chat,
monoforumChannel,
@ -204,6 +204,7 @@ const Chat: FC<OwnProps & StateProps> = ({
const { isForum, isForumAsMessages, isMonoforum } = chat || {};
const listedTopicIds = topicsInfo?.listedTopicIds;
const shouldForceNonForumView = chat?.isBotForum && listedTopicIds && !listedTopicIds.length;
useEnsureMessage(isSavedDialog ? currentUserId : chatId, lastMessageId, lastMessage);
@ -373,10 +374,10 @@ const Chat: FC<OwnProps & StateProps> = ({
// Load the forum topics to display unread count badge
useEffect(() => {
if (isIntersecting && isForum && isSynced && listedTopicIds === undefined) {
if (isIntersecting && isForum && isSynced && (!topicsInfo || topicsInfo.isCache)) {
loadTopics({ chatId });
}
}, [chatId, listedTopicIds, isSynced, isForum, isIntersecting]);
}, [chatId, topicsInfo, isSynced, isForum, isIntersecting]);
const isOnline = user && userStatus && isUserOnline(user, userStatus);
const { hasShownClass: isAvatarOnlineShown } = useShowTransitionDeprecated(isOnline);
@ -598,7 +599,7 @@ export default memo(withGlobal<OwnProps>(
lastMessage,
lastMessageId,
currentUserId: global.currentUserId!,
listedTopicIds: topicsInfo?.listedTopicIds,
topicsInfo,
isSynced: global.isSynced,
lastMessageStory,
isAccountFrozen,

View File

@ -34,7 +34,6 @@ import { mapTruthyValues, mapValues } from '../../../../util/iteratees';
import useSelector from '../../../../hooks/data/useSelector';
import useAppLayout from '../../../../hooks/useAppLayout';
import useEffectWithPrevDeps from '../../../../hooks/useEffectWithPrevDeps';
import useHistoryBack from '../../../../hooks/useHistoryBack';
import useInfiniteScroll from '../../../../hooks/useInfiniteScroll';
import { useIntersectionObserver, useOnIntersect } from '../../../../hooks/useIntersectionObserver';
@ -97,13 +96,11 @@ const ForumPanel = ({
const { isMobile } = useAppLayout();
const chatId = chat?.id;
useEffectWithPrevDeps(([prevIsSynced]) => {
if (!isSynced) return;
const hasJustSynced = prevIsSynced === false;
if (chatId && (hasJustSynced || !topicsInfo)) {
loadTopics({ chatId, force: hasJustSynced });
}
}, [isSynced, chatId, topicsInfo]);
useEffect(() => {
if (!chatId || !isSynced) return;
if (topicsInfo && !topicsInfo.isCache) return;
loadTopics({ chatId });
}, [chatId, topicsInfo, isSynced]);
const [isScrolled, setIsScrolled] = useState(false);
const lang = useLang();

View File

@ -2622,12 +2622,17 @@ addActionHandler('loadTopics', async (global, actions, payload): Promise<void> =
if (!chat) return;
const topicsInfo = selectTopicsInfo(global, chatId);
const shouldRefreshFromStart = force || topicsInfo?.isCache;
if (!force && topicsInfo?.listedTopicIds && topicsInfo.listedTopicIds.length === topicsInfo.totalCount) {
if (
!shouldRefreshFromStart
&& topicsInfo?.listedTopicIds
&& topicsInfo.listedTopicIds.length === topicsInfo.totalCount
) {
return;
}
const offsetTopic = !force ? topicsInfo?.listedTopicIds?.reduce((acc, el) => {
const offsetTopic = !shouldRefreshFromStart ? topicsInfo?.listedTopicIds?.reduce((acc, el) => {
const topicThreadInfo = selectThreadInfo(global, chatId, el);
const accTopicThreadInfo = selectThreadInfo(global, chatId, acc);
if (!topicThreadInfo?.lastMessageId) return acc;
@ -2649,12 +2654,22 @@ addActionHandler('loadTopics', async (global, actions, payload): Promise<void> =
if (!result) return;
global = getGlobal();
const updatedTopicsInfo = selectTopicsInfo(global, chatId);
if (updatedTopicsInfo?.isCache) { // Reset local state
global = updateTopicsInfo(global, chatId, {
topicsById: {},
listedTopicIds: [],
orderedPinnedTopicIds: undefined,
});
}
global = addMessages(global, result.messages);
result.topics.forEach((topic) => {
global = updateTopicWithState(global, chatId, topic);
});
global = updateTopicsInfo(global, chatId, {
totalCount: result.count,
isCache: undefined,
});
global = updateListedTopicIds(global, chatId, result.topics.map((topicState) => topicState.topic.id));
Object.entries(result.draftsById || {}).forEach(([threadId, draft]) => {

View File

@ -5,7 +5,7 @@ import type {
ApiAvailableReaction,
ApiMessage,
} from '../api/types';
import type { MessageList, ThreadId } from '../types';
import type { MessageList, ThreadId, TopicsInfo } from '../types';
import type { ActionReturnType, GlobalState, SharedState } from './types';
import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../api/types';
@ -649,10 +649,25 @@ function reduceChats<T extends GlobalState>(global: T): GlobalState['chats'] {
all: pickTruthy(global.chats.lastMessageIds.all || {}, idsToSave),
saved: global.chats.lastMessageIds.saved,
},
topicsInfoById: pickTruthy(global.chats.topicsInfoById, currentChatIds),
topicsInfoById: reduceTopicsInfo(global.chats.topicsInfoById, currentChatIds),
};
}
function reduceTopicsInfo(
topicsInfoById: Record<string, TopicsInfo>, chatIds: string[],
): GlobalState['chats']['topicsInfoById'] {
const topicsInfoToSave = pickTruthy(topicsInfoById, chatIds);
return Object.entries(topicsInfoToSave).reduce((acc, [chatId, topicsInfo]) => {
acc[chatId] = {
...topicsInfo,
isCache: true,
};
return acc;
}, {} as GlobalState['chats']['topicsInfoById']);
}
function getTopPeerIds<T extends GlobalState>(global: T) {
return unique(Object.values(global.topPeerCategories).flatMap((category) => category?.peerIds || []));
}

View File

@ -674,6 +674,7 @@ export interface ServiceNotification {
export interface TopicsInfo {
totalCount: number;
isCache?: true;
topicsById: Record<ThreadId, ApiTopic>;
listedTopicIds?: number[];
orderedPinnedTopicIds?: number[];