diff --git a/examples/vite/src/App.tsx b/examples/vite/src/App.tsx
index d15533eed..adfd3e738 100644
--- a/examples/vite/src/App.tsx
+++ b/examples/vite/src/App.tsx
@@ -96,22 +96,4 @@ const Threads = () => {
);
};
-/* {activeThread && (
-
-
-
-
-
- )} */
-
-// const Wrapper = () => {
-// const { activeThread } = useThreadContext();
-
-// console.log({ activeThread });
-
-// if (!activeThread) return;
-
-// return ;
-// };
-
export default App;
diff --git a/examples/vite/src/index.scss b/examples/vite/src/index.scss
index 082c23e3f..fee29480a 100644
--- a/examples/vite/src/index.scss
+++ b/examples/vite/src/index.scss
@@ -16,10 +16,10 @@ body,
display: flex;
height: 100%;
- .str-chat__thread-list {
- width: 50%;
- height: 100%;
- }
+ // .str-chat__thread-list {
+ // width: 50%;
+ // height: 100%;
+ // }
.str-chat__thread-list-item {
all: unset;
@@ -99,7 +99,7 @@ body,
.str-chat__thread {
width: 100%;
height: 100%;
- position: fixed;
+ position: absolute;
z-index: 1;
}
@@ -122,6 +122,18 @@ body,
}
}
+ .str-chat.threads {
+ display: flex;
+ height: 100%;
+ width: 100%;
+
+ .vml {
+ display: flex;
+ flex-direction: column;
+ width: 70%;
+ }
+ }
+
@media screen and (min-width: 768px) {
//.str-chat__channel-list.thread-open {
// &.menu-open {
@@ -146,11 +158,6 @@ body,
position: initial;
z-index: 0;
}
-
- .str-chat__thread {
- position: initial;
- z-index: 0;
- }
}
@media screen and (min-width: 1024px) {
@@ -162,8 +169,8 @@ body,
.str-chat__thread {
width: 45%;
- //position: initial;
- //z-index: 0;
+ position: initial;
+ z-index: 0;
}
.str-chat__channel-header .str-chat__header-hamburger {
diff --git a/src/components/Channel/Channel.tsx b/src/components/Channel/Channel.tsx
index 0f6b2f9f4..2779bea5a 100644
--- a/src/components/Channel/Channel.tsx
+++ b/src/components/Channel/Channel.tsx
@@ -98,6 +98,7 @@ import type { URLEnrichmentConfig } from '../MessageInput/hooks/useLinkPreviews'
import { defaultReactionOptions, ReactionOptions } from '../Reactions';
import { EventComponent } from '../EventComponent';
import { DateSeparator } from '../DateSeparator';
+import { useThreadContext } from '../Threads';
type ChannelPropsForwardedToComponentContext<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
@@ -399,6 +400,8 @@ const ChannelInner = <
windowsEmojiClass,
} = useChannelContainerClasses({ customClasses });
+ const thread = useThreadContext();
+
const [channelConfig, setChannelConfig] = useState(channel.getConfig());
const [notifications, setNotifications] = useState([]);
const [quotedMessage, setQuotedMessage] = useState>();
@@ -1055,20 +1058,22 @@ const ChannelInner = <
channel.state.filterErrorMessages();
const messagePreview = {
- __html: text,
attachments,
created_at: new Date(),
html: text,
id: customMessageData?.id ?? `${client.userID}-${nanoid()}`,
mentioned_users,
+ parent_id: parent?.id,
reactions: [],
status: 'sending',
text,
type: 'regular',
user: client.user,
- ...(parent?.id ? { parent_id: parent.id } : null),
};
+ // @ts-expect-error
+ if (thread.channel) thread.upsertReply({ message: messagePreview });
+
updateMessage(messagePreview);
await doSendMessage(messagePreview, customMessageData, options);
diff --git a/src/components/Message/hooks/useReactionHandler.ts b/src/components/Message/hooks/useReactionHandler.ts
index e057dceb6..7a0349890 100644
--- a/src/components/Message/hooks/useReactionHandler.ts
+++ b/src/components/Message/hooks/useReactionHandler.ts
@@ -11,6 +11,8 @@ import type { Reaction, ReactionResponse } from 'stream-chat';
import type { DefaultStreamChatGenerics } from '../../../types/types';
+import { useThreadContext } from '../../Threads';
+
export const reactionHandlerWarning = `Reaction handler was called, but it is missing one of its required arguments.
Make sure the ChannelAction and ChannelState contexts are properly set and the hook is initialized with a valid message.`;
@@ -19,6 +21,7 @@ export const useReactionHandler = <
>(
message?: StreamMessage,
) => {
+ const thread = useThreadContext();
const { updateMessage } = useChannelActionContext('useReactionHandler');
const { channel, channelCapabilities } = useChannelStateContext(
'useReactionHandler',
@@ -93,15 +96,18 @@ export const useReactionHandler = <
try {
updateMessage(tempMessage);
+ if (thread.channel) thread.upsertReply({ message: tempMessage });
const messageResponse = add
? await channel.sendReaction(id, { type } as Reaction)
: await channel.deleteReaction(id, type);
+ // seems useless as we're expecting WS event to come in and replace this anyway
updateMessage(messageResponse.message);
} catch (error) {
// revert to the original message if the API call fails
updateMessage(message);
+ if (thread.channel) thread.upsertReply({ message });
}
}, 1000);
diff --git a/src/components/Thread/Thread.tsx b/src/components/Thread/Thread.tsx
index 448b678ba..a21df4169 100644
--- a/src/components/Thread/Thread.tsx
+++ b/src/components/Thread/Thread.tsx
@@ -105,7 +105,7 @@ const ThreadInner = <
thread,
threadHasMore,
threadLoadingMore,
- threadMessages,
+ threadMessages = [],
threadSuppressAutoscroll,
} = useChannelStateContext('Thread');
const { closeThread, loadMoreThread } = useChannelActionContext('Thread');
@@ -164,7 +164,7 @@ const ThreadInner = <
loadMore={parentMessage ? threadInstance.loadPreviousPage : loadMoreThread}
Message={MessageUIComponent}
messageActions={messageActions}
- messages={threadInstance.channel ? latestReplies : threadMessages ?? []}
+ messages={parentMessage ? latestReplies : threadMessages}
suppressAutoscroll={threadSuppressAutoscroll}
threadList
{...(virtualized ? additionalVirtualizedMessageListProps : additionalMessageListProps)}
diff --git a/src/components/Threads/ThreadContext.tsx b/src/components/Threads/ThreadContext.tsx
index 86225e082..5be9ab568 100644
--- a/src/components/Threads/ThreadContext.tsx
+++ b/src/components/Threads/ThreadContext.tsx
@@ -1,4 +1,4 @@
-import React, { createContext, useContext } from 'react';
+import React, { createContext, useContext, useMemo } from 'react';
import { Channel } from '../../components';
@@ -6,6 +6,11 @@ import type { PropsWithChildren } from 'react';
import { Thread } from 'stream-chat';
import { useChatContext } from '../../context';
+/**
+ * TODO:
+ * - make it easier for current state of the SDK to use thread methods (meaning thread should be fully initialized and checks like `thread.channel && ...` shouldn't exist (these checks are due to a "placeholder" which is used anytime components are utilised under normal circumstances - not under ThreadContext)
+ */
+
export type ThreadContextValue = Thread | undefined;
export const ThreadContext = createContext(undefined);
@@ -14,7 +19,12 @@ export const useThreadContext = () => {
const { client } = useChatContext();
const thread = useContext(ThreadContext);
- if (!thread) return new Thread({ client, registerEventHandlers: false, threadData: {} });
+ const placeholder = useMemo(
+ () => new Thread({ client, registerEventHandlers: false, threadData: {} }),
+ [client],
+ );
+
+ if (!thread) return placeholder;
return thread;
};
@@ -25,17 +35,6 @@ export const ThreadProvider = ({ children, thread }: PropsWithChildren<{ thread?
);
-// export const ThreadFacilitator = ({
-// children,
-// thread,
-// }: PropsWithChildren<{
-// thread: Thread;
-// }>) => {
-//
-// {children};
-// ;
-// };
-
/**
*
* client {
diff --git a/src/components/Threads/ThreadList/ThreadList.tsx b/src/components/Threads/ThreadList/ThreadList.tsx
index 6e744b35b..4b67d465a 100644
--- a/src/components/Threads/ThreadList/ThreadList.tsx
+++ b/src/components/Threads/ThreadList/ThreadList.tsx
@@ -1,9 +1,8 @@
import React, { useEffect } from 'react';
import { ComputeItemKey, Virtuoso } from 'react-virtuoso';
-import { StreamChat } from 'stream-chat';
import type { ComponentType, PointerEvent } from 'react';
-import type { InferStoreValueType, Thread } from 'stream-chat';
+import type { InferStoreValueType, Thread, ThreadManager } from 'stream-chat';
import { ThreadListItem } from './ThreadListItem';
import { useChatContext } from '../../../context';
@@ -11,19 +10,29 @@ import { useSimpleStateStore } from '../hooks/useSimpleStateStore';
import type { ThreadListItemProps } from './ThreadListItem';
-const selector = (v: InferStoreValueType) => [v.threads] as const;
+/**
+ * TODO:
+ * - add virtuosoProps override \w support for supplying custom threads array (typed virtuoso props, Thread[] only)
+ * - register event handlers of each of the threads (ThreadManager - threads.EventHandlers(), maybe simplify API)
+ * - add Header with re-query button + logic to ThreadManager
+ * - add Footer with "Loading"
+ * - move str-chat someplace else, think of a different name (str-chat__thread-list already used)
+ * - move itemContent to a component instead
+ *
+ * NICE TO HAVE:
+ * - probably good idea to move component context up to a Chat component
+ */
+
+const selector = (nextValue: InferStoreValueType) => [nextValue.threads] as const;
const computeItemKey: ComputeItemKey = (_, item) => item.id;
type ThreadListProps = {
onItemPointerDown?: (event: PointerEvent, thread: Thread) => void;
ThreadListItem?: ComponentType;
- // TODO: should support supplying custom threads array
// threads?: Thread[]
};
-// TODO: probably good idea to move component context up to a Chat component
-
export const ThreadList = ({
ThreadListItem: PropsThreadListItem = ThreadListItem,
onItemPointerDown,
@@ -37,11 +46,7 @@ export const ThreadList = ({
return (
- // TODO: figure out - handle next page load blocking client-side or here?
- atBottom && client.threads.loadNextPage()
- }
- // TODO: str-chat class name does not belong here, str-chat__thread-list is already used (FUCK ME SIDEWAYS)
+ atBottomStateChange={(atBottom) => atBottom && client.threads.loadNextPage()}
className='str-chat str-chat__thread-list'
computeItemKey={computeItemKey}
data={threads}