Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: initial message load issues #2292

Merged
merged 7 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,10 @@ Array of allowed message actions (ex: 'edit', 'delete', 'reply'). To disable all

The limit to use when paginating new messages (the page size).

:::caution
After mounting, the `MessageList` component checks if the list is completely filled with messages. If there is some space left in the list, `MessageList` will load the next page of messages, but it will do so _only once_. This means that if your `messageLimit` is too low, or if your viewport is very large, the list will not be completely filled. Set the limit with this in mind.
:::

| Type | Default |
| ------ | ------- |
| number | 100 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ Custom UI component to display an individual message.

The limit to use when paginating messages (the page size).

:::caution
After mounting, the `VirtualizedMessageList` component checks if the list is completely filled with messages. If there is some space left in the list, `VirtualizedMessageList` will load the next page of messages, but it will do so _only once_. This means that if your `messageLimit` is too low, or if your viewport is very large, the list will not be completely filled. Set the limit with this in mind.
:::

| Type | Default |
| ------ | ------- |
| number | 100 |
Expand Down
2 changes: 1 addition & 1 deletion examples/typescript/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import * as serviceWorker from './serviceWorker';
createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
</React.StrictMode>,
);

// If you want your app to work offline and load faster, you can change
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"isomorphic-ws": "^4.0.1",
"linkifyjs": "^4.1.0",
"lodash.debounce": "^4.0.8",
"lodash.defaultsdeep": "^4.6.1",
"lodash.throttle": "^4.1.1",
"lodash.uniqby": "^4.7.0",
"nanoid": "^3.3.4",
Expand Down Expand Up @@ -155,6 +156,7 @@
"@types/jsdom": "^21.1.5",
"@types/linkifyjs": "^2.1.3",
"@types/lodash.debounce": "^4.0.7",
"@types/lodash.defaultsdeep": "^4.6.9",
"@types/lodash.throttle": "^4.1.7",
"@types/lodash.uniqby": "^4.7.7",
"@types/moment": "^2.13.0",
Expand Down
25 changes: 20 additions & 5 deletions src/components/Channel/Channel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import React, {
} from 'react';

import debounce from 'lodash.debounce';
import defaultsDeep from 'lodash.defaultsdeep';
import throttle from 'lodash.throttle';
import {
ChannelAPIResponse,
Expand Down Expand Up @@ -346,7 +347,7 @@ const ChannelInner = <
acceptedFiles,
activeUnreadHandler,
channel,
channelQueryOptions,
channelQueryOptions: propChannelQueryOptions,
children,
doDeleteMessageRequest,
doMarkReadRequest,
Expand All @@ -366,6 +367,16 @@ const ChannelInner = <
skipMessageDataMemoization,
} = props;

const channelQueryOptions: ChannelQueryOptions<StreamChatGenerics> & {
messages: { limit: number };
} = useMemo(
() =>
defaultsDeep(propChannelQueryOptions, {
messages: { limit: DEFAULT_INITIAL_CHANNEL_PAGE_SIZE },
}),
[propChannelQueryOptions],
);
Comment on lines +370 to +378
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use DEFAULT_INITIAL_CHANNEL_PAGE_SIZE when loading the first page of messages.


const {
client,
customClasses,
Expand Down Expand Up @@ -546,6 +557,7 @@ const ChannelInner = <
useLayoutEffect(() => {
let errored = false;
let done = false;
let channelInitializedExternally = true;

(async () => {
if (!channel.initialized && initializeOnMount) {
Expand All @@ -571,6 +583,7 @@ 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 @@ -583,10 +596,12 @@ const ChannelInner = <
if (!errored) {
dispatch({
channel,
hasMore: hasMoreMessagesProbably(
channel.state.messages.length,
channelQueryOptions?.messages?.limit ?? DEFAULT_INITIAL_CHANNEL_PAGE_SIZE,
),
hasMore:
channelInitializedExternally ||
hasMoreMessagesProbably(
channel.state.messages.length,
channelQueryOptions.messages.limit,
),
Comment on lines -586 to +604
Copy link
Contributor Author

@myandrienko myandrienko Feb 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it wasn't the Channel component itself that loaded the first page of messages, don't trust the message count to determine if there's more messages to load, since the function that loaded messages could have totally different limits.

type: 'initStateFromChannel',
});

Expand Down
2 changes: 1 addition & 1 deletion src/components/Channel/__tests__/Channel.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ describe('Channel', () => {

await waitFor(() => {
expect(watchSpy).toHaveBeenCalledTimes(1);
expect(watchSpy).toHaveBeenCalledWith(undefined);
expect(watchSpy).toHaveBeenCalledWith({ messages: { limit: 25 } });
});
});

Expand Down
2 changes: 2 additions & 0 deletions src/components/ChannelList/hooks/usePaginatedChannels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useChatContext } from '../../../context/ChatContext';

import type { DefaultStreamChatGenerics } from '../../../types/types';
import type { ChannelsQueryState } from '../../Chat/hooks/useChannelsQueryState';
import { DEFAULT_INITIAL_CHANNEL_PAGE_SIZE } from '../../../constants/limits';

const RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS = 5000;
const MIN_RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS = 2000;
Expand Down Expand Up @@ -79,6 +80,7 @@ export const usePaginatedChannels = <

const newOptions = {
limit: options?.limit ?? MAX_QUERY_CHANNELS_LIMIT,
message_limit: options?.message_limit ?? DEFAULT_INITIAL_CHANNEL_PAGE_SIZE,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use DEFAULT_INITIAL_CHANNEL_PAGE_SIZE to limit the number of messages when loading channel list.

offset,
...options,
};
Expand Down
20 changes: 8 additions & 12 deletions src/components/InfiniteScrollPaginator/InfiniteScroll.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { PropsWithChildren, useCallback, useEffect, useLayoutEffect, useRef } from 'react';
import React, { PropsWithChildren, useEffect, useLayoutEffect, useRef } from 'react';
import type { PaginatorProps } from '../../types/types';
import { deprecationAndReplacementWarning } from '../../utils/deprecationWarning';

Expand Down Expand Up @@ -73,7 +73,8 @@ export const InfiniteScroll = (props: PropsWithChildren<InfiniteScrollProps>) =>

const scrollComponent = useRef<HTMLElement>();

const scrollListener = useCallback(() => {
const scrollListenerRef = useRef<() => void>();
scrollListenerRef.current = () => {
const element = scrollComponent.current;

if (!element || element.offsetParent === null) {
Expand Down Expand Up @@ -103,15 +104,7 @@ export const InfiniteScroll = (props: PropsWithChildren<InfiniteScrollProps>) =>
if (offset < Number(threshold) && typeof loadNextPageFn === 'function' && hasNextPageFlag) {
loadNextPageFn();
}
}, [
hasPreviousPageFlag,
hasNextPageFlag,
isLoading,
listenToScroll,
loadPreviousPageFn,
loadNextPageFn,
threshold,
]);
};

useEffect(() => {
deprecationAndReplacementWarning(
Expand All @@ -130,14 +123,17 @@ export const InfiniteScroll = (props: PropsWithChildren<InfiniteScrollProps>) =>
const scrollElement = scrollComponent.current?.parentNode;
if (!scrollElement) return;

const scrollListener = () => scrollListenerRef.current?.();

scrollElement.addEventListener('scroll', scrollListener, useCapture);
scrollElement.addEventListener('resize', scrollListener, useCapture);
scrollListener();
Copy link
Contributor Author

@myandrienko myandrienko Feb 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that we'll only load a single additional page of messages in case the first page is not enough to cause overflow of the message list.

To keep loading pages until there is overflow, or there is no more pages to load, we can do something like this:

  useLayoutEffect(() => {
    if (!isLoading) {
      scrollListenerRef.current?.();
    }
  }, [isLoading]);

However, I don't see a way to have the same behavior in the VirtualizedMessageList, and since our default page size is rather big (100 messages), I think it's better to keep things consistent.


return () => {
scrollElement.removeEventListener('scroll', scrollListener, useCapture);
scrollElement.removeEventListener('resize', scrollListener, useCapture);
};
}, [initialLoad, scrollListener, useCapture]);
}, [initialLoad, useCapture]);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prevent this effect from firing excessively. Note that scrollListener is now a ref, so it always has access to the latest props.


useEffect(() => {
const scrollElement = scrollComponent.current?.parentNode;
Expand Down
3 changes: 2 additions & 1 deletion src/components/MessageList/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import type { MessageProps } from '../Message/types';
import type { StreamMessage } from '../../context/ChannelStateContext';

import type { DefaultStreamChatGenerics } from '../../types/types';
import { DEFAULT_NEXT_CHANNEL_PAGE_SIZE } from '../../constants/limits';

type MessageListWithContextProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
Expand Down Expand Up @@ -68,7 +69,7 @@ const MessageListWithContext = <
headerPosition,
read,
renderMessages = defaultRenderMessages,
messageLimit = 100,
messageLimit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove hard-coded limit for message page size.

loadMore: loadMoreCallback,
loadMoreNewer: loadMoreNewerCallback,
hasMoreNewer = false,
Expand Down
7 changes: 5 additions & 2 deletions src/components/MessageList/VirtualizedMessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { ComponentContextValue, useComponentContext } from '../../context/Compon

import type { Channel, ChannelState as StreamChannelState, UserResponse } from 'stream-chat';
import type { DefaultStreamChatGenerics, UnknownType } from '../../types/types';
import { DEFAULT_NEXT_CHANNEL_PAGE_SIZE } from '../../constants/limits';

type VirtualizedMessageListPropsForContext =
| 'additionalMessageInputProps'
Expand Down Expand Up @@ -184,7 +185,7 @@ const VirtualizedMessageListWithContext = <
loadMoreNewer,
Message: MessageUIComponentFromProps,
messageActions,
messageLimit = 100,
messageLimit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove hard-coded limit for message page size.

messages,
notifications,
// TODO: refactor to scrollSeekPlaceHolderConfiguration and components.ScrollSeekPlaceholder, like the Virtuoso Component
Expand Down Expand Up @@ -386,7 +387,9 @@ const VirtualizedMessageListWithContext = <
}
};
const atTopStateChange = (isAtTop: boolean) => {
if (isAtTop) loadMore?.(messageLimit);
if (isAtTop) {
loadMore?.(messageLimit);
}
};

useEffect(() => {
Expand Down
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2497,6 +2497,13 @@
dependencies:
"@types/lodash" "*"

"@types/lodash.defaultsdeep@^4.6.9":
version "4.6.9"
resolved "https://registry.yarnpkg.com/@types/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.9.tgz#050fbe389a7a6e245b15da9ee83a8a62f047a1c4"
integrity sha512-pLtCFK0YkHfGtGLYLNMTbFB5/G5+RsmQCIbbHH8GOAXjv+gDkVilY98kILfe8JH2Kev0OCReYxp1AjxEjP8ixA==
dependencies:
"@types/lodash" "*"

"@types/lodash.throttle@^4.1.7":
version "4.1.7"
resolved "https://registry.yarnpkg.com/@types/lodash.throttle/-/lodash.throttle-4.1.7.tgz#4ef379eb4f778068022310ef166625f420b6ba58"
Expand Down Expand Up @@ -9391,6 +9398,11 @@ lodash.deburr@^4.1.0:
resolved "https://registry.yarnpkg.com/lodash.deburr/-/lodash.deburr-4.1.0.tgz#ddb1bbb3ef07458c0177ba07de14422cb033ff9b"
integrity sha1-3bG7s+8HRYwBd7oH3hRCLLAz/5s=

lodash.defaultsdeep@^4.6.1:
version "4.6.1"
resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6"
integrity sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==

lodash.escaperegexp@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
Expand Down