Skip to content

Commit

Permalink
fix: use the client pagination indicators for ChannelStateContext's h…
Browse files Browse the repository at this point in the history
…asMore and hasMoreNewer flags (#2478)
  • Loading branch information
MartinCupela authored Aug 22, 2024
1 parent 3a9acea commit eb13bd5
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 51 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@
"rollup-plugin-url": "^3.0.1",
"rollup-plugin-visualizer": "^4.2.0",
"semantic-release": "^19.0.5",
"stream-chat": "^8.33.1",
"stream-chat": "8.39.0",
"style-loader": "^2.0.0",
"ts-jest": "^28.0.8",
"typescript": "^4.7.4",
Expand Down
61 changes: 21 additions & 40 deletions src/components/Channel/Channel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,11 @@ const ChannelInner = <
channelReducer,
// channel.initialized === false if client.channel().query() was not called, e.g. ChannelList is not used
// => Channel will call channel.watch() in useLayoutEffect => state.loading is used to signal the watch() call state
{ ...initialState, loading: !channel.initialized },
{
...initialState,
hasMore: channel.state.messagePagination.hasPrev,
loading: !channel.initialized,
},
);

const isMounted = useIsMounted();
Expand Down Expand Up @@ -571,7 +575,6 @@ const ChannelInner = <
useLayoutEffect(() => {
let errored = false;
let done = false;
let channelInitializedExternally = true;

(async () => {
if (!channel.initialized && initializeOnMount) {
Expand All @@ -597,7 +600,6 @@ const ChannelInner = <
await getChannel({ channel, client, members, options: channelQueryOptions });
const config = channel.getConfig();
setChannelConfig(config);
channelInitializedExternally = false;
} catch (e) {
dispatch({ error: e as Error, type: 'setError' });
errored = true;
Expand All @@ -610,12 +612,7 @@ const ChannelInner = <
if (!errored) {
dispatch({
channel,
hasMore:
channelInitializedExternally ||
hasMoreMessagesProbably(
channel.state.messages.length,
channelQueryOptions.messages.limit,
),
hasMore: channel.state.messagePagination.hasPrev,
type: 'initStateFromChannel',
});

Expand Down Expand Up @@ -690,7 +687,8 @@ const ChannelInner = <
);

const loadMore = async (limit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE) => {
if (!online.current || !window.navigator.onLine || !state.hasMore) return 0;
if (!online.current || !window.navigator.onLine || !channel.state.messagePagination.hasPrev)
return 0;

// prevent duplicate loading events...
const oldestMessage = state?.messages?.[0];
Expand All @@ -716,14 +714,14 @@ const ChannelInner = <
return 0;
}

const hasMoreMessages = queryResponse.messages.length === perPage;
loadMoreFinished(hasMoreMessages, channel.state.messages);
loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);

return queryResponse.messages.length;
};

const loadMoreNewer = async (limit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE) => {
if (!online.current || !window.navigator.onLine || !state.hasMoreNewer) return 0;
if (!online.current || !window.navigator.onLine || !channel.state.messagePagination.hasNext)
return 0;

const newestMessage = state?.messages?.[state?.messages?.length - 1];
if (state.loadingMore || state.loadingMoreNewer) return 0;
Expand All @@ -745,10 +743,8 @@ const ChannelInner = <
return 0;
}

const hasMoreNewerMessages = channel.state.messages !== channel.state.latestMessages;

dispatch({
hasMoreNewer: hasMoreNewerMessages,
hasMoreNewer: channel.state.messagePagination.hasNext,
messages: channel.state.messages,
type: 'loadMoreNewerFinished',
});
Expand All @@ -766,18 +762,9 @@ const ChannelInner = <
dispatch({ loadingMore: true, type: 'setLoadingMore' });
await channel.state.loadMessageIntoState(messageId, undefined, messageLimit);

/**
* if the message we are jumping to has less than half of the page size older messages,
* we have jumped to the beginning of the channel.
*/
const indexOfMessage = channel.state.messages.findIndex(
(message) => message.id === messageId,
);
const hasMoreMessages = indexOfMessage >= Math.floor(messageLimit / 2);

loadMoreFinished(hasMoreMessages, channel.state.messages);
loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
dispatch({
hasMoreNewer: channel.state.messages !== channel.state.latestMessages,
hasMoreNewer: channel.state.messagePagination.hasNext,
highlightedMessageId: messageId,
type: 'jumpToMessageFinished',
});
Expand All @@ -796,9 +783,7 @@ const ChannelInner = <

const jumpToLatestMessage: ChannelActionContextValue<StreamChatGenerics>['jumpToLatestMessage'] = useCallback(async () => {
await channel.state.loadMessageIntoState('latest');
// FIXME: we cannot rely on constant value 25 as the page size can be customized by integrators
const hasMoreOlder = channel.state.messages.length >= 25;
loadMoreFinished(hasMoreOlder, channel.state.messages);
loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
dispatch({
type: 'jumpToLatestMessage',
});
Expand All @@ -813,7 +798,6 @@ const ChannelInner = <
let lastReadMessageId = channelUnreadUiState?.last_read_message_id;
let firstUnreadMessageId = channelUnreadUiState?.first_unread_message_id;
let isInCurrentMessageSet = false;
let hasMoreMessages = true;

if (firstUnreadMessageId) {
const result = findInMsgSetById(firstUnreadMessageId, channel.state.messages);
Expand Down Expand Up @@ -852,14 +836,14 @@ const ChannelInner = <
).messages;
} catch (e) {
addNotification(t('Failed to jump to the first unread message'), 'error');
loadMoreFinished(hasMoreMessages, channel.state.messages);
loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
return;
}

const firstMessageWithCreationDate = messages.find((msg) => msg.created_at);
if (!firstMessageWithCreationDate) {
addNotification(t('Failed to jump to the first unread message'), 'error');
loadMoreFinished(hasMoreMessages, channel.state.messages);
loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
return;
}
const firstMessageTimestamp = new Date(
Expand All @@ -868,13 +852,11 @@ const ChannelInner = <
if (lastReadTimestamp < firstMessageTimestamp) {
// whole channel is unread
firstUnreadMessageId = firstMessageWithCreationDate.id;
hasMoreMessages = false;
} else {
const result = findInMsgSetByDate(channelUnreadUiState.last_read, messages);
lastReadMessageId = result.target?.id;
hasMoreMessages = result.index >= Math.floor(queryMessageLimit / 2);
}
loadMoreFinished(hasMoreMessages, channel.state.messages);
loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
}
}

Expand All @@ -895,13 +877,12 @@ const ChannelInner = <
const indexOfTarget = channel.state.messages.findIndex(
(message) => message.id === targetId,
) as number;
hasMoreMessages = indexOfTarget >= Math.floor(queryMessageLimit / 2);
loadMoreFinished(hasMoreMessages, channel.state.messages);
loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
firstUnreadMessageId =
firstUnreadMessageId ?? channel.state.messages[indexOfTarget + 1]?.id;
} catch (e) {
addNotification(t('Failed to jump to the first unread message'), 'error');
loadMoreFinished(hasMoreMessages, channel.state.messages);
loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
return;
}
}
Expand All @@ -918,7 +899,7 @@ const ChannelInner = <
});

dispatch({
hasMoreNewer: channel.state.messages !== channel.state.latestMessages,
hasMoreNewer: channel.state.messagePagination.hasNext,
highlightedMessageId: firstUnreadMessageId,
type: 'jumpToMessageFinished',
});
Expand Down
36 changes: 30 additions & 6 deletions src/components/Channel/__tests__/Channel.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ const ActiveChannelSetter = ({ activeChannel }) => {
const user = generateUser({ custom: 'custom-value', id: 'id', name: 'name' });

// create a full message state so that we can properly test `loadMore`
const messages = Array.from({ length: 25 }, () => generateMessage({ user }));
const messages = Array.from({ length: 25 }, (_, i) =>
generateMessage({ created_at: new Date((i + 1) * 1000000), user }),
);

const pinnedMessages = [generateMessage({ pinned: true, user })];

Expand Down Expand Up @@ -701,6 +703,20 @@ describe('Channel', () => {

describe('loading more messages', () => {
const limit = 10;
it("should initiate the hasMore flag with the current message set's pagination hasPrev value", async () => {
const { channel, chatClient } = await initClient();
let hasMore;
await renderComponent({ channel, chatClient }, ({ hasMore: hasMoreCtx }) => {
hasMore = hasMoreCtx;
});
expect(hasMore).toBe(true);

channel.state.messageSets[0].pagination.hasPrev = false;
await renderComponent({ channel, chatClient }, ({ hasMore: hasMoreCtx }) => {
hasMore = hasMoreCtx;
});
expect(hasMore).toBe(false);
});
it('should be able to load more messages', async () => {
const { channel, chatClient } = await initClient();
const channelQuerySpy = jest.spyOn(channel, 'query');
Expand Down Expand Up @@ -740,7 +756,7 @@ describe('Channel', () => {
it('should set hasMore to false if querying channel returns less messages than the limit', async () => {
const { channel, chatClient } = await initClient();
let channelHasMore = false;
const newMessages = [generateMessage()];
const newMessages = [generateMessage({ created_at: new Date(1000) })];
await renderComponent(
{ channel, chatClient },
({ hasMore, loadMore, messages: contextMessages }) => {
Expand Down Expand Up @@ -822,8 +838,12 @@ describe('Channel', () => {

it('should load the second page, if the previous query has returned message count equal default messages limit', async () => {
const { channel, chatClient } = await initClient();
const firstPageMessages = Array.from({ length: 25 }, generateMessage);
const secondPageMessages = Array.from({ length: 15 }, generateMessage);
const firstPageMessages = Array.from({ length: 25 }, (_, i) =>
generateMessage({ created_at: new Date((i + 16) * 100000) }),
);
const secondPageMessages = Array.from({ length: 15 }, (_, i) =>
generateMessage({ created_at: new Date((i + 1) * 100000) }),
);
useMockedApis(chatClient, [queryChannelWithNewMessages(firstPageMessages, channel)]);
let queryNextPageSpy;
let contextMessageCount;
Expand Down Expand Up @@ -896,8 +916,12 @@ describe('Channel', () => {
const channelQueryOptions = {
messages: { limit: equalCount },
};
const firstPageMessages = Array.from({ length: equalCount }, generateMessage);
const secondPageMessages = Array.from({ length: equalCount - 1 }, generateMessage);
const firstPageMessages = Array.from({ length: equalCount }, (_, i) =>
generateMessage({ created_at: new Date((i + 1 + equalCount) * 100000) }),
);
const secondPageMessages = Array.from({ length: equalCount - 1 }, (_, i) =>
generateMessage({ created_at: new Date((i + 1) * 100000) }),
);
useMockedApis(chatClient, [queryChannelWithNewMessages(firstPageMessages, channel)]);
let queryNextPageSpy;
let contextMessageCount;
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -13292,10 +13292,10 @@ stream-browserify@^2.0.1:
inherits "~2.0.1"
readable-stream "^2.0.2"

stream-chat@^8.33.1:
version "8.33.1"
resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.33.1.tgz#d4e7f3bb10ac4564572431922c97e7c4eda7fe3b"
integrity sha512-r4vUjjsBTtCER2wEFYJzbgSY7eipPkM9gyQNV5VVdZhegY/NggeinwY1bYpBXBpQ5JIEvNFIWWRPOFYkMae3MQ==
stream-chat@8.39.0:
version "8.39.0"
resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.39.0.tgz#f4cb86bd5cac4c1272c24cd66ed4752bcda8d717"
integrity sha512-zQZR1tPrgGBbu+Gnv9F9KQx3OPUMvb0FN+39BEjkjgjRPm2JYhF78jfcYutQMiC538t3V+NgFGgj5N4sZvSsUA==
dependencies:
"@babel/runtime" "^7.16.3"
"@types/jsonwebtoken" "~9.0.0"
Expand Down

0 comments on commit eb13bd5

Please sign in to comment.