stream-chat-react
Version:
React components to create chat conversations or livestream style chat
88 lines (87 loc) • 5.13 kB
JavaScript
import React, { useEffect } from 'react';
import clsx from 'clsx';
import { LegacyThreadContext } from './LegacyThreadContext';
import { MESSAGE_ACTIONS } from '../Message';
import { MessageInput, MessageInputFlat } from '../MessageInput';
import { MessageList, VirtualizedMessageList } from '../MessageList';
import { ThreadHeader as DefaultThreadHeader } from './ThreadHeader';
import { ThreadHead as DefaultThreadHead } from '../Thread/ThreadHead';
import { useChannelActionContext, useChannelStateContext, useChatContext, useComponentContext, } from '../../context';
import { useThreadContext } from '../Threads';
import { useStateStore } from '../../store';
/**
* The Thread component renders a parent Message with a list of replies
*/
export const Thread = (props) => {
const { channel, channelConfig, thread } = useChannelStateContext('Thread');
const threadInstance = useThreadContext();
if (!thread && !threadInstance)
return null;
if (channelConfig?.replies === false)
return null;
// the wrapper ensures a key variable is set and the component recreates on thread switch
return (
// FIXME: TS is having trouble here as at least one of the two would always be defined
React.createElement(ThreadInner, { ...props, key: `thread-${(thread ?? threadInstance)?.id}-${channel?.cid}` }));
};
const selector = (nextValue) => ({
isLoadingNext: nextValue.pagination.isLoadingNext,
isLoadingPrev: nextValue.pagination.isLoadingPrev,
parentMessage: nextValue.parentMessage,
replies: nextValue.replies,
});
const ThreadInner = (props) => {
const { additionalMessageInputProps, additionalMessageListProps, additionalParentMessageProps, additionalVirtualizedMessageListProps, autoFocus = true, enableDateSeparator = false, Input: PropInput, Message: PropMessage, messageActions = Object.keys(MESSAGE_ACTIONS), virtualized, } = props;
const threadInstance = useThreadContext();
const { thread, threadHasMore, threadLoadingMore, threadMessages = [], threadSuppressAutoscroll, } = useChannelStateContext('Thread');
const { closeThread, loadMoreThread } = useChannelActionContext('Thread');
const { customClasses } = useChatContext('Thread');
const { Message: ContextMessage, ThreadHead = DefaultThreadHead, ThreadHeader = DefaultThreadHeader, ThreadInput: ContextInput, VirtualMessage, } = useComponentContext('Thread');
const { isLoadingNext, isLoadingPrev, parentMessage, replies } = useStateStore(threadInstance?.state, selector) ?? {};
const ThreadInput = PropInput ?? additionalMessageInputProps?.Input ?? ContextInput ?? MessageInputFlat;
const ThreadMessage = PropMessage || additionalMessageListProps?.Message;
const FallbackMessage = virtualized && VirtualMessage ? VirtualMessage : ContextMessage;
const MessageUIComponent = ThreadMessage || FallbackMessage;
const ThreadMessageList = virtualized ? VirtualizedMessageList : MessageList;
useEffect(() => {
if (threadInstance)
return;
if ((thread?.reply_count ?? 0) > 0) {
// FIXME: integrators can customize channel query options but cannot customize channel.getReplies() options
loadMoreThread();
}
}, [thread, loadMoreThread, threadInstance]);
const threadProps = threadInstance
? {
loadingMore: isLoadingPrev,
loadingMoreNewer: isLoadingNext,
loadMore: threadInstance.loadPrevPage,
loadMoreNewer: threadInstance.loadNextPage,
messages: replies,
}
: {
hasMore: threadHasMore,
loadingMore: threadLoadingMore,
loadMore: loadMoreThread,
messages: threadMessages,
};
const messageAsThread = thread ?? parentMessage;
if (!messageAsThread)
return null;
const threadClass = customClasses?.thread ||
clsx('str-chat__thread-container str-chat__thread', {
'str-chat__thread--virtualized': virtualized,
});
const head = (React.createElement(ThreadHead, { key: messageAsThread.id, message: messageAsThread, Message: MessageUIComponent, ...additionalParentMessageProps }));
return (
// Thread component needs a context which we can use for message composer
React.createElement(LegacyThreadContext.Provider, { value: {
legacyThread: thread ?? undefined,
} },
React.createElement("div", { className: threadClass },
React.createElement(ThreadHeader, { closeThread: closeThread, thread: messageAsThread }),
React.createElement(ThreadMessageList, { disableDateSeparator: !enableDateSeparator, head: head, Message: MessageUIComponent, messageActions: messageActions, suppressAutoscroll: threadSuppressAutoscroll, threadList: true, ...threadProps, ...(virtualized
? additionalVirtualizedMessageListProps
: additionalMessageListProps) }),
React.createElement(MessageInput, { focus: autoFocus, Input: ThreadInput, isThreadInput: true, parent: thread ?? parentMessage, ...additionalMessageInputProps }))));
};