UNPKG

stream-chat-react

Version:

React components to create chat conversations or livestream style chat

88 lines (87 loc) 5.13 kB
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 })))); };