diff --git a/src/hooks/useUnmount.ts b/src/hooks/useUnmount.ts index 4f51ead8e..a307fae25 100644 --- a/src/hooks/useUnmount.ts +++ b/src/hooks/useUnmount.ts @@ -1,10 +1,14 @@ -import { DependencyList, useLayoutEffect } from 'react'; +import { useLayoutEffect, useRef } from 'react'; // this hook accepts a callback to run component is unmounted -export function useUnmount(callback: () => void, deps: DependencyList = []) { +export function useUnmount(callback: () => void) { + const callbackRef = useRef(callback); + + callbackRef.current = callback; + useLayoutEffect(() => { return () => { - callback(); + callbackRef.current(); }; - }, deps); + }, []); } diff --git a/src/lib/Sendbird/context/SendbirdProvider.tsx b/src/lib/Sendbird/context/SendbirdProvider.tsx index 588d17638..a3f9d24bd 100644 --- a/src/lib/Sendbird/context/SendbirdProvider.tsx +++ b/src/lib/Sendbird/context/SendbirdProvider.tsx @@ -125,7 +125,7 @@ const SendbirdContextManager = ({ // Disconnect on unmount useUnmount(() => { actions.disconnect({ logger }); - }, [sdkStore]); + }); // should move to reducer const [currentTheme, setCurrentTheme] = useState(theme); diff --git a/src/lib/Sendbird/context/hooks/useSendbird.tsx b/src/lib/Sendbird/context/hooks/useSendbird.tsx index ac9659406..439441ae2 100644 --- a/src/lib/Sendbird/context/hooks/useSendbird.tsx +++ b/src/lib/Sendbird/context/hooks/useSendbird.tsx @@ -1,5 +1,5 @@ import { useSyncExternalStore } from 'use-sync-external-store/shim'; -import { useContext, useMemo } from 'react'; +import { useCallback, useContext, useMemo } from 'react'; import { SendbirdError, User } from '@sendbird/chat'; import { SendbirdContext } from '../SendbirdContext'; @@ -11,27 +11,20 @@ const NO_CONTEXT_ERROR = 'No sendbird state value available. Make sure you are r export const useSendbird = () => { const store = useContext(SendbirdContext); if (!store) throw new Error(NO_CONTEXT_ERROR); - const state: SendbirdState = useSyncExternalStore(store.subscribe, store.getState); - const actions = useMemo(() => ({ - /* Example: How to set the state basically */ - // exampleAction: () => { - // store.setState((state): SendbirdState => ({ - // ...state, - // example: true, - // })), - // }, - - /* AppInfo */ - initMessageTemplateInfo: ({ payload }: { payload: MessageTemplatesInfo }) => { + + /* AppInfo */ + const appInfoActions = { + initMessageTemplateInfo: useCallback(({ payload }: { payload: MessageTemplatesInfo }) => { store.setState((state): SendbirdState => ( updateAppInfoStore(state, { messageTemplatesInfo: payload, waitingTemplateKeysMap: {}, }) )); - }, - upsertMessageTemplates: ({ payload }) => { + }, [store]), + + upsertMessageTemplates: useCallback(({ payload }) => { const appInfoStore = state.stores.appInfoStore; const templatesInfo = appInfoStore.messageTemplatesInfo; if (!templatesInfo) return state; // Not initialized. Ignore. @@ -48,8 +41,9 @@ export const useSendbird = () => { messageTemplatesInfo: templatesInfo, }) )); - }, - upsertWaitingTemplateKeys: ({ payload }) => { + }, [store, state.stores.appInfoStore]), + + upsertWaitingTemplateKeys: useCallback(({ payload }) => { const appInfoStore = state.stores.appInfoStore; const { keys, requestedAt } = payload; const waitingTemplateKeysMap = { ...appInfoStore.waitingTemplateKeysMap }; @@ -64,8 +58,9 @@ export const useSendbird = () => { waitingTemplateKeysMap, }) )); - }, - markErrorWaitingTemplateKeys: ({ payload }) => { + }, [store, state.stores.appInfoStore]), + + markErrorWaitingTemplateKeys: useCallback(({ payload }) => { const appInfoStore = state.stores.appInfoStore; const { keys, messageId } = payload; const waitingTemplateKeysMap = { ...appInfoStore.waitingTemplateKeysMap }; @@ -80,18 +75,21 @@ export const useSendbird = () => { waitingTemplateKeysMap, }) )); - }, + }, [store, state.stores.appInfoStore]), + }; - /* SDK */ - setSdkLoading: (payload) => { + /* SDK */ + const sdkActions = { + setSdkLoading: useCallback((payload) => { store.setState((state): SendbirdState => ( updateSdkStore(state, { initialized: false, loading: payload, }) )); - }, - sdkError: () => { + }, [store]), + + sdkError: useCallback(() => { store.setState((state): SendbirdState => ( updateSdkStore(state, { initialized: false, @@ -99,8 +97,9 @@ export const useSendbird = () => { error: true, }) )); - }, - initSdk: (payload) => { + }, [store]), + + initSdk: useCallback((payload) => { store.setState((state): SendbirdState => ( updateSdkStore(state, { sdk: payload, @@ -109,8 +108,9 @@ export const useSendbird = () => { error: false, }) )); - }, - resetSdk: () => { + }, [store]), + + resetSdk: useCallback(() => { store.setState((state): SendbirdState => ( updateSdkStore(state, { sdk: {} as SdkStore['sdk'], @@ -119,10 +119,12 @@ export const useSendbird = () => { error: false, }) )); - }, + }, [store]), + }; - /* User */ - initUser: (payload) => { + /* User */ + const userActions = { + initUser: useCallback((payload) => { store.setState((state): SendbirdState => ( updateUserStore(state, { initialized: true, @@ -130,8 +132,9 @@ export const useSendbird = () => { user: payload, }) )); - }, - resetUser: () => { + }, [store]), + + resetUser: useCallback(() => { store.setState((state): SendbirdState => ( updateUserStore(state, { initialized: false, @@ -139,93 +142,119 @@ export const useSendbird = () => { user: {} as User, }) )); - }, - updateUserInfo: (payload: User) => { + }, [store]), + + updateUserInfo: useCallback((payload: User) => { store.setState((state): SendbirdState => ( updateUserStore(state, { user: payload, }) )); - }, - - /* Connection */ - connect: async (params) => { - const { - logger, - userId, - appId, - accessToken, - nickname, - profileUrl, - isMobile, - sdkInitParams, - customApiHost, - customWebSocketHost, - customExtensionParams, - eventHandlers, - initializeMessageTemplatesInfo, - configureSession, - initDashboardConfigs, - } = params; - - // clean up previous ws connection - await actions.disconnect({ logger }); - - const sdk = initSDK({ - appId, - customApiHost, - customWebSocketHost, - sdkInitParams, - }); + }, [store]), + }; - setupSDK(sdk, { - logger, - isMobile, - customExtensionParams, - sessionHandler: configureSession ? configureSession(sdk) : undefined, - }); + /* Connection */ + const disconnect = useCallback(async ({ logger }: { logger: LoggerInterface }) => { + sdkActions.setSdkLoading(true); - actions.setSdkLoading(true); + const sdk = state.stores.sdkStore.sdk; - try { - const user = await sdk.connect(userId, accessToken); - actions.initUser(user); + if (sdk?.disconnectWebSocket) { + await sdk.disconnectWebSocket(); + } - if (nickname || profileUrl) { - await sdk.updateCurrentUserInfo({ - nickname: nickname || user.nickname || '', - profileUrl: profileUrl || user.profileUrl, - }); - } + sdkActions.resetSdk(); + userActions.resetUser(); + logger.info?.('SendbirdProvider | useSendbird/disconnect completed'); + }, [ + store, + state.stores.sdkStore?.sdk, + sdkActions, + userActions, + ]); - await initializeMessageTemplatesInfo?.(sdk); - await initDashboardConfigs?.(sdk); + const connect = useCallback(async (params) => { + const { + logger, + userId, + appId, + accessToken, + nickname, + profileUrl, + isMobile, + sdkInitParams, + customApiHost, + customWebSocketHost, + customExtensionParams, + eventHandlers, + initializeMessageTemplatesInfo, + configureSession, + initDashboardConfigs, + } = params; - actions.initSdk(sdk); + // clean up previous ws connection + await disconnect({ logger }); - eventHandlers?.connection?.onConnected?.(user); - } catch (error) { - const sendbirdError = error as SendbirdError; - actions.resetSdk(); - actions.resetUser(); - logger.error?.('SendbirdProvider | useSendbird/connect failed', sendbirdError); - eventHandlers?.connection?.onFailed?.(sendbirdError); - } - }, - disconnect: async ({ logger }: { logger: LoggerInterface }) => { - actions.setSdkLoading(true); + const sdk = initSDK({ + appId, + customApiHost, + customWebSocketHost, + sdkInitParams, + }); + + setupSDK(sdk, { + logger, + isMobile, + customExtensionParams, + sessionHandler: configureSession ? configureSession(sdk) : undefined, + }); - const sdk = state.stores.sdkStore.sdk; + sdkActions.setSdkLoading(true); - if (sdk?.disconnectWebSocket) { - await sdk.disconnectWebSocket(); + try { + const user = await sdk.connect(userId, accessToken); + userActions.initUser(user); + + if (nickname || profileUrl) { + await sdk.updateCurrentUserInfo({ + nickname: nickname || user.nickname || '', + profileUrl: profileUrl || user.profileUrl, + }); } - actions.resetSdk(); - actions.resetUser(); - logger.info?.('SendbirdProvider | useSendbird/disconnect completed'); - }, - }), [store, state.stores.sdkStore?.sdk, state.stores.appInfoStore]); + await initializeMessageTemplatesInfo?.(sdk); + await initDashboardConfigs?.(sdk); + + sdkActions.initSdk(sdk); + + eventHandlers?.connection?.onConnected?.(user); + } catch (error) { + const sendbirdError = error as SendbirdError; + sdkActions.resetSdk(); + userActions.resetUser(); + logger.error?.('SendbirdProvider | useSendbird/connect failed', sendbirdError); + eventHandlers?.connection?.onFailed?.(sendbirdError); + } + }, [ + store, + sdkActions, + userActions, + disconnect, + ]); + + const actions = useMemo(() => ({ + ...appInfoActions, + ...sdkActions, + ...userActions, + disconnect, + connect, + }), [ + appInfoActions, + sdkActions, + userActions, + disconnect, + connect, + ]); return { state, actions }; }; diff --git a/src/modules/CreateChannel/context/useCreateChannel.ts b/src/modules/CreateChannel/context/useCreateChannel.ts index b65d42a80..222722995 100644 --- a/src/modules/CreateChannel/context/useCreateChannel.ts +++ b/src/modules/CreateChannel/context/useCreateChannel.ts @@ -1,5 +1,5 @@ import { useSyncExternalStore } from 'use-sync-external-store/shim'; -import { useContext, useMemo } from 'react'; +import { useCallback, useContext, useMemo } from 'react'; import { CreateChannelContext, CreateChannelState } from './CreateChannelProvider'; import { CHANNEL_TYPE } from '../types'; import { getCreateGroupChannel } from '../../../lib/selectors'; @@ -10,20 +10,30 @@ const useCreateChannel = () => { const sendbirdStore = useSendbirdStateContext(); if (!store) throw new Error('useCreateChannel must be used within a CreateChannelProvider'); + const setPageStep = useCallback((pageStep: number) => { + store.setState(state => ({ ...state, pageStep })); + }, [store]); + + const setType = useCallback((type: CHANNEL_TYPE) => { + store.setState(state => ({ ...state, type })); + }, [store]); + + const createChannel = useCallback((...params: Parameters>) => { + const createChannel = getCreateGroupChannel(sendbirdStore); + + return createChannel(...params); + }, [sendbirdStore]); + const state: CreateChannelState = useSyncExternalStore(store.subscribe, store.getState); const actions = useMemo(() => ({ - setPageStep: (pageStep: number) => store.setState(state => ({ - ...state, - pageStep, - })), - - setType: (type: CHANNEL_TYPE) => store.setState(state => ({ - ...state, - type, - })), - - createChannel: getCreateGroupChannel(sendbirdStore), - }), [store]); + setPageStep, + setType, + createChannel, + }), [ + setPageStep, + setType, + createChannel, + ]); return { state, actions }; }; diff --git a/src/modules/MessageSearch/context/hooks/useMessageSearch.ts b/src/modules/MessageSearch/context/hooks/useMessageSearch.ts index 41baca12d..d877876e5 100644 --- a/src/modules/MessageSearch/context/hooks/useMessageSearch.ts +++ b/src/modules/MessageSearch/context/hooks/useMessageSearch.ts @@ -1,5 +1,5 @@ import { useSyncExternalStore } from 'use-sync-external-store/shim'; -import { useContext, useMemo } from 'react'; +import { useCallback, useContext, useMemo } from 'react'; import type { GroupChannel } from '@sendbird/chat/groupChannel'; import { MessageSearchQuery } from '@sendbird/chat/message'; @@ -11,73 +11,93 @@ const useMessageSearch = () => { if (!store) throw new Error('useMessageSearch must be used within a MessageSearchProvider'); const state: MessageSearchState = useSyncExternalStore(store.subscribe, store.getState); - const actions = useMemo(() => ({ - setCurrentChannel: (channel: GroupChannel) => store.setState(state => ({ - ...state, - currentChannel: channel, - initialized: true, - })), - setChannelInvalid: () => store.setState(state => ({ - ...state, - currentChannel: null, - initialized: false, - })), - - getSearchedMessages: (messages: ClientSentMessages[], createdQuery: MessageSearchQuery) => { - store.setState(state => { - if (createdQuery && createdQuery.channelUrl === state.currentMessageSearchQuery?.channelUrl - && (createdQuery as any).key === (state.currentMessageSearchQuery as any).key) { - return { - ...state, - loading: false, - isInvalid: false, - allMessages: messages, - hasMoreResult: state.currentMessageSearchQuery.hasNext, - }; - } - return state; - }); - }, - - setQueryInvalid: () => store.setState(state => ({ ...state, isInvalid: true })), - - startMessageSearch: () => store.setState(state => ({ - ...state, - isInvalid: false, - loading: false, - })), + const setCurrentChannel = useCallback((channel: GroupChannel) => { + store.setState(state => ({ ...state, currentChannel: channel, initialized: true })); + }, [store]); - startGettingSearchedMessages: (query: MessageSearchQuery) => store.setState(state => ({ - ...state, - loading: true, - currentMessageSearchQuery: query, - })), + const setChannelInvalid = useCallback(() => { + store.setState(state => ({ ...state, currentChannel: null, initialized: false })); + }, [store]); + + const getSearchedMessages = useCallback((messages: ClientSentMessages[], createdQuery: MessageSearchQuery) => { + store.setState(state => { + if (createdQuery && createdQuery.channelUrl === state.currentMessageSearchQuery?.channelUrl + && (createdQuery as any).key === (state.currentMessageSearchQuery as any).key) { + return { + ...state, + loading: false, + isInvalid: false, + allMessages: messages, + hasMoreResult: state.currentMessageSearchQuery.hasNext, + }; + } + return state; + }); + }, [store]); + + const setQueryInvalid = useCallback(() => { + store.setState(state => ({ ...state, isInvalid: true })); + }, [store]); - getNextSearchedMessages: (messages: ClientSentMessages[]) => store.setState(state => ({ + const startMessageSearch = useCallback(() => { + store.setState(state => ({ ...state, isInvalid: false, loading: false })); + }, [store]); + + const startGettingSearchedMessages = useCallback((query: MessageSearchQuery) => { + store.setState(state => ({ ...state, loading: true, currentMessageSearchQuery: query })); + }, [store]); + + const getNextSearchedMessages = useCallback((messages: ClientSentMessages[]) => { + store.setState(state => ({ ...state, allMessages: [...state.allMessages, ...messages], hasMoreResult: state.currentMessageSearchQuery?.hasNext || false, - })), + })); + }, [store]); - resetSearchString: () => store.setState(state => ({ ...state, allMessages: [] })), + const resetSearchString = useCallback(() => { + store.setState(state => ({ ...state, allMessages: [] })); + }, [store]); - setSelectedMessageId: (messageId: number) => store.setState(state => ({ - ...state, - selectedMessageId: messageId, - })), + const setSelectedMessageId = (messageId: number) => useCallback(() => { + store.setState(state => ({ ...state, selectedMessageId: messageId })); + }, [store]); - handleRetryToConnect: () => store.setState(state => ({ - ...state, - retryCount: state.retryCount + 1, - })), + const handleRetryToConnect = useCallback(() => { + store.setState(state => ({ ...state, retryCount: state.retryCount + 1 })); + }, [store]); - // Looks exactly same as handleRetryToConnect but keep just for backward compatibility - setRetryCount: () => store.setState(state => ({ - ...state, - retryCount: state.retryCount + 1, - })), - }), [store]); + // Looks exactly same as handleRetryToConnect but keep just for backward compatibility + const setRetryCount = useCallback(() => { + store.setState(state => ({ ...state, retryCount: state.retryCount + 1 })); + }, [store]); + + const actions = useMemo(() => ({ + setCurrentChannel, + setChannelInvalid, + getSearchedMessages, + setQueryInvalid, + startMessageSearch, + startGettingSearchedMessages, + getNextSearchedMessages, + resetSearchString, + setSelectedMessageId, + handleRetryToConnect, + setRetryCount, + }), [ + setCurrentChannel, + setChannelInvalid, + getSearchedMessages, + setQueryInvalid, + startMessageSearch, + startGettingSearchedMessages, + getNextSearchedMessages, + resetSearchString, + setSelectedMessageId, + handleRetryToConnect, + setRetryCount, + ]); return { state, actions }; }; diff --git a/src/modules/Thread/context/hooks/useDeleteMessageCallback.ts b/src/modules/Thread/context/hooks/useDeleteMessageCallback.ts index e5d888ceb..23449d0fc 100644 --- a/src/modules/Thread/context/hooks/useDeleteMessageCallback.ts +++ b/src/modules/Thread/context/hooks/useDeleteMessageCallback.ts @@ -47,5 +47,9 @@ export default function useDeleteMessageCallback({ reject(err); }); }); - }, [currentChannel]); + }, [ + currentChannel, + onMessageDeletedByReqId, + onMessageDeleted, + ]); } diff --git a/src/modules/Thread/context/hooks/useResendMessageCallback.ts b/src/modules/Thread/context/hooks/useResendMessageCallback.ts index 07497fb11..70e4d98a9 100644 --- a/src/modules/Thread/context/hooks/useResendMessageCallback.ts +++ b/src/modules/Thread/context/hooks/useResendMessageCallback.ts @@ -140,5 +140,10 @@ export default function useResendMessageCallback({ failedMessage.sendingStatus = SendingStatus.FAILED; sendMessageFailure(failedMessage); } - }, [currentChannel]); + }, [ + currentChannel, + resendMessageStart, + sendMessageSuccess, + sendMessageFailure, + ]); } diff --git a/src/modules/Thread/context/hooks/useSendFileMessage.ts b/src/modules/Thread/context/hooks/useSendFileMessage.ts index fd38846ab..864077f4a 100644 --- a/src/modules/Thread/context/hooks/useSendFileMessage.ts +++ b/src/modules/Thread/context/hooks/useSendFileMessage.ts @@ -88,6 +88,11 @@ export default function useSendFileMessageCallback({ } }); }, - [currentChannel], + [ + currentChannel, + onBeforeSendFileMessage, + sendMessageStart, + sendMessageFailure, + ], ); } diff --git a/src/modules/Thread/context/hooks/useSendUserMessageCallback.ts b/src/modules/Thread/context/hooks/useSendUserMessageCallback.ts index b7681d516..949773abd 100644 --- a/src/modules/Thread/context/hooks/useSendUserMessageCallback.ts +++ b/src/modules/Thread/context/hooks/useSendUserMessageCallback.ts @@ -85,6 +85,12 @@ export default function useSendUserMessageCallback({ }); }); } - }, [isMentionEnabled, currentChannel]); + }, [ + isMentionEnabled, + currentChannel, + onBeforeSendUserMessage, + sendMessageStart, + sendMessageFailure, + ]); return sendMessage; } diff --git a/src/modules/Thread/context/hooks/useSendVoiceMessageCallback.ts b/src/modules/Thread/context/hooks/useSendVoiceMessageCallback.ts index fbe62974e..f850f1047 100644 --- a/src/modules/Thread/context/hooks/useSendVoiceMessageCallback.ts +++ b/src/modules/Thread/context/hooks/useSendVoiceMessageCallback.ts @@ -103,6 +103,8 @@ export const useSendVoiceMessageCallback = ({ }, [ currentChannel, onBeforeSendVoiceMessage, + sendMessageStart, + sendMessageFailure, ]); return sendMessage; }; diff --git a/src/modules/Thread/context/hooks/useThreadFetchers.ts b/src/modules/Thread/context/hooks/useThreadFetchers.ts index a3ea176e2..f34da3a2b 100644 --- a/src/modules/Thread/context/hooks/useThreadFetchers.ts +++ b/src/modules/Thread/context/hooks/useThreadFetchers.ts @@ -74,7 +74,15 @@ export const useThreadFetchers = ({ initializeThreadListFailure(); } }, - [stores.sdkStore.initialized, staleParentMessage, anchorMessage, isReactionEnabled], + [ + stores.sdkStore.initialized, + staleParentMessage, + anchorMessage, + isReactionEnabled, + initializeThreadListStart, + initializeThreadListSuccess, + initializeThreadListFailure, + ], ); const loadPrevious = useCallback( @@ -96,7 +104,15 @@ export const useThreadFetchers = ({ getPrevMessagesFailure(); } }, - [threadListState, oldestMessageTimeStamp, isReactionEnabled, staleParentMessage], + [ + threadListState, + oldestMessageTimeStamp, + isReactionEnabled, + staleParentMessage, + getPrevMessagesStart, + getPrevMessagesSuccess, + getPrevMessagesFailure, + ], ); const loadNext = useCallback( @@ -117,7 +133,15 @@ export const useThreadFetchers = ({ getNextMessagesFailure(); } }, - [threadListState, latestMessageTimeStamp, isReactionEnabled, staleParentMessage], + [ + threadListState, + latestMessageTimeStamp, + isReactionEnabled, + staleParentMessage, + getNextMessagesStart, + getNextMessagesSuccess, + getNextMessagesFailure, + ], ); return { diff --git a/src/modules/Thread/context/hooks/useUpdateMessageCallback.ts b/src/modules/Thread/context/hooks/useUpdateMessageCallback.ts index 17881c824..0ebde8ac4 100644 --- a/src/modules/Thread/context/hooks/useUpdateMessageCallback.ts +++ b/src/modules/Thread/context/hooks/useUpdateMessageCallback.ts @@ -79,5 +79,9 @@ export default function useUpdateMessageCallback({ }, ); }); - }, [currentChannel, isMentionEnabled]); + }, [ + currentChannel, + isMentionEnabled, + onMessageUpdated, + ]); } diff --git a/src/modules/Thread/context/useThread.ts b/src/modules/Thread/context/useThread.ts index 853c4c232..2ec79319c 100644 --- a/src/modules/Thread/context/useThread.ts +++ b/src/modules/Thread/context/useThread.ts @@ -1,5 +1,5 @@ import { useSyncExternalStore } from 'use-sync-external-store/shim'; -import { useContext, useMemo } from 'react'; +import { useCallback, useContext, useMemo } from 'react'; import { ThreadContext, ThreadState } from './ThreadProvider'; import { ChannelStateTypes, FileUploadInfoParams, ParentMessageStateTypes, ThreadListStateTypes } from '../types'; import { GroupChannel, Member } from '@sendbird/chat/groupChannel'; @@ -36,7 +36,7 @@ const useThread = () => { const store = useContext(ThreadContext); if (!store) throw new Error('useThread must be used within a ThreadProvider'); // SendbirdStateContext config - const { state: { stores, config } } = useSendbird(); + const { state: { config } } = useSendbird(); const { logger, pubSub } = config; const isMentionEnabled = config.groupChannel.enableMention; const isReactionEnabled = config.groupChannel.enableReactions; @@ -55,7 +55,7 @@ const useThread = () => { } = state; const sendMessageStatusActions = { - sendMessageStart: (message: SendableMessageType) => store.setState(state => { + sendMessageStart: useCallback((message: SendableMessageType) => store.setState(state => { return { ...state, localThreadMessages: [ @@ -63,9 +63,9 @@ const useThread = () => { message, ], }; - }), + }), [store]), - sendMessageSuccess: (message: SendableMessageType) => store.setState(state => { + sendMessageSuccess: useCallback((message: SendableMessageType) => store.setState(state => { return { ...state, allThreadMessages: [ @@ -78,9 +78,9 @@ const useThread = () => { !compareIds((m as UserMessage)?.reqId, message?.reqId) )), }; - }), + }), [store]), - sendMessageFailure: (message: SendableMessageType) => store.setState(state => { + sendMessageFailure: useCallback((message: SendableMessageType) => store.setState(state => { return { ...state, localThreadMessages: state.localThreadMessages.map((m) => ( @@ -89,9 +89,9 @@ const useThread = () => { : m )), }; - }), + }), [store]), - resendMessageStart: (message: SendableMessageType) => store.setState(state => { + resendMessageStart: useCallback((message: SendableMessageType) => store.setState(state => { return { ...state, localThreadMessages: state.localThreadMessages.map((m) => ( @@ -100,7 +100,7 @@ const useThread = () => { : m )), }; - }), + }), [store]), }; const toggleReaction = useToggleReactionCallback({ currentChannel }, { logger }); @@ -155,53 +155,59 @@ const useThread = () => { }; const messageModifiedActions = { - onMessageUpdated: (channel: GroupChannel, message: SendableMessageType) => store.setState(state => { - if (state.currentChannel?.url !== channel?.url) { - return state; - } - - return { - ...state, - parentMessage: state.parentMessage?.messageId === message?.messageId - ? message - : state.parentMessage, - allThreadMessages: state.allThreadMessages?.map((msg) => ( - (msg?.messageId === message?.messageId) ? message : msg - )), - }; - }), + onMessageUpdated: useCallback((channel: GroupChannel, message: SendableMessageType) => { + store.setState(state => { + if (state.currentChannel?.url !== channel?.url) { + return state; + } - onMessageDeleted: (channel: GroupChannel, messageId: number) => store.setState(state => { - if (state.currentChannel?.url !== channel?.url) { - return state; - } - if (state?.parentMessage?.messageId === messageId) { return { ...state, - parentMessage: null, - parentMessageState: ParentMessageStateTypes.NIL, - allThreadMessages: [], + parentMessage: state.parentMessage?.messageId === message?.messageId + ? message + : state.parentMessage, + allThreadMessages: state.allThreadMessages?.map((msg) => ( + (msg?.messageId === message?.messageId) ? message : msg + )), }; - } - return { - ...state, - allThreadMessages: state.allThreadMessages?.filter((msg) => ( - msg?.messageId !== messageId - )), - localThreadMessages: state.localThreadMessages?.filter((msg) => ( - msg?.messageId !== messageId - )), - }; - }), + }); + }, [store]), + + onMessageDeleted: useCallback((channel: GroupChannel, messageId: number) => { + store.setState(state => { + if (state.currentChannel?.url !== channel?.url) { + return state; + } + if (state?.parentMessage?.messageId === messageId) { + return { + ...state, + parentMessage: null, + parentMessageState: ParentMessageStateTypes.NIL, + allThreadMessages: [], + }; + } + return { + ...state, + allThreadMessages: state.allThreadMessages?.filter((msg) => ( + msg?.messageId !== messageId + )), + localThreadMessages: state.localThreadMessages?.filter((msg) => ( + msg?.messageId !== messageId + )), + }; + }); + }, [store]), - onMessageDeletedByReqId: (reqId: string | number) => store.setState(state => { - return { - ...state, - localThreadMessages: state.localThreadMessages.filter((m) => ( - !compareIds((m as SendableMessageType).reqId, reqId) - )), - }; - }), + onMessageDeletedByReqId: useCallback((reqId: string | number) => { + store.setState(state => { + return { + ...state, + localThreadMessages: state.localThreadMessages.filter((m) => ( + !compareIds((m as SendableMessageType).reqId, reqId) + )), + }; + }); + }, [store]), }; const modifyMessageActions = { @@ -219,15 +225,15 @@ const useThread = () => { }; const threadFetcherStatusActions = { - initializeThreadListStart: () => store.setState(state => { + initializeThreadListStart: useCallback(() => store.setState(state => { return { ...state, threadListState: ThreadListStateTypes.LOADING, allThreadMessages: [], }; - }), + }), [store]), - initializeThreadListSuccess: (parentMessage: BaseMessage, anchorMessage: SendableMessageType, threadedMessages: BaseMessage[]) => store.setState(state => { + initializeThreadListSuccess: useCallback((parentMessage: BaseMessage, anchorMessage: SendableMessageType, threadedMessages: BaseMessage[]) => store.setState(state => { const anchorMessageCreatedAt = (!anchorMessage?.messageId) ? parentMessage?.createdAt : anchorMessage?.createdAt; const anchorIndex = threadedMessages.findIndex((message) => message?.createdAt > anchorMessageCreatedAt); const prevThreadMessages = anchorIndex > -1 ? threadedMessages.slice(0, anchorIndex) : threadedMessages; @@ -240,57 +246,57 @@ const useThread = () => { hasMoreNext: threadedMessages.length - anchorIndex === NEXT_THREADS_FETCH_SIZE, allThreadMessages: [prevThreadMessages, anchorThreadMessage, nextThreadMessages].flat() as CoreMessageType[], }; - }), + }), [store]), - initializeThreadListFailure: () => store.setState(state => { + initializeThreadListFailure: useCallback(() => store.setState(state => { return { ...state, threadListState: ThreadListStateTypes.LOADING, allThreadMessages: [], }; - }), + }), [store]), - getPrevMessagesStart: () => store.setState(state => { + getPrevMessagesStart: useCallback(() => store.setState(state => { return { ...state, }; - }), + }), [store]), - getPrevMessagesSuccess: (threadedMessages: CoreMessageType[]) => store.setState(state => { + getPrevMessagesSuccess: useCallback((threadedMessages: CoreMessageType[]) => store.setState(state => { return { ...state, hasMorePrev: threadedMessages.length === PREV_THREADS_FETCH_SIZE, allThreadMessages: [...threadedMessages, ...state.allThreadMessages], }; - }), + }), [store]), - getPrevMessagesFailure: () => store.setState(state => { + getPrevMessagesFailure: useCallback(() => store.setState(state => { return { ...state, hasMorePrev: false, }; - }), + }), [store]), - getNextMessagesStart: () => store.setState(state => { + getNextMessagesStart: useCallback(() => store.setState(state => { return { ...state, }; - }), + }), [store]), - getNextMessagesSuccess: (threadedMessages: CoreMessageType[]) => store.setState(state => { + getNextMessagesSuccess: useCallback((threadedMessages: CoreMessageType[]) => store.setState(state => { return { ...state, hasMoreNext: threadedMessages.length === NEXT_THREADS_FETCH_SIZE, allThreadMessages: [...state.allThreadMessages, ...threadedMessages], }; - }), + }), [store]), - getNextMessagesFailure: () => store.setState(state => { + getNextMessagesFailure: useCallback(() => store.setState(state => { return { ...state, hasMoreNext: false, }; - }), + }), [store]), }; const { initializeThreadFetcher, fetchPrevThreads, fetchNextThreads } = useThreadFetchers({ @@ -313,57 +319,57 @@ const useThread = () => { getNextMessagesFailure: threadFetcherStatusActions.getNextMessagesFailure, }); - const actions = useMemo(() => ({ - setCurrentUserId: (currentUserId: string) => store.setState(state => ({ + const simpleActions = { + setCurrentUserId: useCallback((currentUserId: string) => store.setState(state => ({ ...state, currentUserId: currentUserId, - })), + })), [store]), - getChannelStart: () => store.setState(state => ({ + getChannelStart: useCallback(() => store.setState(state => ({ ...state, channelState: ChannelStateTypes.LOADING, currentChannel: null, - })), + })), [store]), - getChannelSuccess: (groupChannel: GroupChannel) => store.setState(state => ({ + getChannelSuccess: useCallback((groupChannel: GroupChannel) => store.setState(state => ({ ...state, channelState: ChannelStateTypes.INITIALIZED, currentChannel: groupChannel, // only support in normal group channel isMuted: groupChannel?.members?.find((member) => member?.userId === state.currentUserId)?.isMuted || false, isChannelFrozen: groupChannel?.isFrozen || false, - })), + })), [store]), - getChannelFailure: () => store.setState(state => ({ + getChannelFailure: useCallback(() => store.setState(state => ({ ...state, channelState: ChannelStateTypes.INVALID, currentChannel: null, - })), + })), [store]), - getParentMessageStart: () => store.setState(state => ({ + getParentMessageStart: useCallback(() => store.setState(state => ({ ...state, parentMessageState: ParentMessageStateTypes.LOADING, parentMessage: null, - })), + })), [store]), - getParentMessageSuccess: (parentMessage: SendableMessageType) => store.setState(state => ({ + getParentMessageSuccess: useCallback((parentMessage: SendableMessageType) => store.setState(state => ({ ...state, parentMessageState: ParentMessageStateTypes.INITIALIZED, parentMessage: parentMessage, - })), + })), [store]), - getParentMessageFailure: () => store.setState(state => ({ + getParentMessageFailure: useCallback(() => store.setState(state => ({ ...state, parentMessageState: ParentMessageStateTypes.INVALID, parentMessage: null, - })), + })), [store]), - setEmojiContainer: (emojiContainer: EmojiContainer) => store.setState(state => ({ + setEmojiContainer: useCallback((emojiContainer: EmojiContainer) => store.setState(state => ({ ...state, emojiContainer: emojiContainer, - })), + })), [store]), - onMessageReceived: (channel: GroupChannel, message: SendableMessageType) => store.setState(state => { + onMessageReceived: useCallback((channel: GroupChannel, message: SendableMessageType) => store.setState(state => { if ( state.currentChannel?.url !== channel?.url || state.hasMoreNext @@ -388,9 +394,9 @@ const useThread = () => { message, ], }; - }), + }), [store]), - onReactionUpdated: (reactionEvent: ReactionEvent) => store.setState(state => { + onReactionUpdated: useCallback((reactionEvent: ReactionEvent) => store.setState(state => { if (state?.parentMessage?.messageId === reactionEvent?.messageId) { state.parentMessage?.applyReactionEvent?.(reactionEvent); } @@ -404,9 +410,9 @@ const useThread = () => { return m; }), }; - }), + }), [store]), - onUserMuted: (channel: GroupChannel, user: User) => store.setState(state => { + onUserMuted: useCallback((channel: GroupChannel, user: User) => store.setState(state => { if (state.currentChannel?.url !== channel?.url || state.currentUserId !== user?.userId) { return state; } @@ -414,9 +420,9 @@ const useThread = () => { ...state, isMuted: true, }; - }), + }), [store]), - onUserUnmuted: (channel: GroupChannel, user: User) => store.setState(state => { + onUserUnmuted: useCallback((channel: GroupChannel, user: User) => store.setState(state => { if (state.currentChannel?.url !== channel?.url || state.currentUserId !== user?.userId) { return state; } @@ -424,9 +430,9 @@ const useThread = () => { ...state, isMuted: false, }; - }), + }), [store]), - onUserBanned: () => store.setState(state => { + onUserBanned: useCallback(() => store.setState(state => { return { ...state, channelState: ChannelStateTypes.NIL, @@ -438,15 +444,15 @@ const useThread = () => { hasMorePrev: false, hasMoreNext: false, }; - }), + }), [store]), - onUserUnbanned: () => store.setState(state => { + onUserUnbanned: useCallback(() => store.setState(state => { return { ...state, }; - }), + }), [store]), - onUserLeft: () => store.setState(state => { + onUserLeft: useCallback(() => store.setState(state => { return { ...state, channelState: ChannelStateTypes.NIL, @@ -458,23 +464,23 @@ const useThread = () => { hasMorePrev: false, hasMoreNext: false, }; - }), + }), [store]), - onChannelFrozen: () => store.setState(state => { + onChannelFrozen: useCallback(() => store.setState(state => { return { ...state, isChannelFrozen: true, }; - }), + }), [store]), - onChannelUnfrozen: () => store.setState(state => { + onChannelUnfrozen: useCallback(() => store.setState(state => { return { ...state, isChannelFrozen: false, }; - }), + }), [store]), - onOperatorUpdated: (channel: GroupChannel) => store.setState(state => { + onOperatorUpdated: useCallback((channel: GroupChannel) => store.setState(state => { if (channel?.url === state.currentChannel?.url) { return { ...state, @@ -482,9 +488,9 @@ const useThread = () => { }; } return state; - }), + }), [store]), - onTypingStatusUpdated: (channel: GroupChannel, typingMembers: Member[]) => store.setState(state => { + onTypingStatusUpdated: useCallback((channel: GroupChannel, typingMembers: Member[]) => store.setState(state => { if (!compareIds(channel.url, state.currentChannel?.url)) { return state; } @@ -492,9 +498,9 @@ const useThread = () => { ...state, typingMembers, }; - }), + }), [store]), - onFileInfoUpdated: ({ + onFileInfoUpdated: useCallback(({ channelUrl, requestId, index, @@ -521,8 +527,11 @@ const useThread = () => { ...state, localThreadMessages, }; - }), + }), [store]), + }; + const actions = useMemo(() => ({ + ...simpleActions, toggleReaction, ...sendMessageStatusActions, ...sendMessageActions, @@ -533,13 +542,16 @@ const useThread = () => { fetchPrevThreads, fetchNextThreads, }), [ - store, - currentChannel, - stores.sdkStore.initialized, - parentMessage, - threadListState, - isReactionEnabled, - logger, + simpleActions, + toggleReaction, + sendMessageStatusActions, + sendMessageActions, + messageModifiedActions, + modifyMessageActions, + threadFetcherStatusActions, + initializeThreadFetcher, + fetchPrevThreads, + fetchNextThreads, ]); return { state, actions };