[Problem/Question]
Hey there! When I init app in useEffect I got error:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'createGroupChannelCollection')
at loadChannels (TripChat.chunk.js:781:38)
at setupUser (TripChat.chunk.js:351:39)
I think that is something wrong with my useEffect, but don’t sure.
This is my code:
import React, { useState, useEffect, useRef } from 'react';
import SendbirdChat from '@sendbird/chat';
import {
GroupChannelModule,
GroupChannelFilter,
GroupChannelListOrder,
MessageFilter,
MessageCollectionInitPolicy,
} from '@sendbird/chat/groupChannel';
import css from './TripChat.module.css';
import { timestampToTime, handleEnterPress } from './utils';
import { useSelector } from 'react-redux';
import { getCurrentUser } from 'ducks/user.duck';
import { ChatTypes } from 'lib/enums';
import ChannelList from 'containers/TripChat/components/ChannelList/ChannelList';
import { injectIntl } from 'react-intl/dist/react-intl';
import Channel from 'containers/TripChat/components/Channel/Channel';
import MessagesList from 'containers/TripChat/components/MessagesList/MessagesList';
import MessageInput from 'containers/TripChat/components/MessageInput/MessageInput';
let sb;
const TripChatComponent = props => {
const [state, updateState] = useState({
applicationUsers: [],
groupChannelMembers: [],
currentlyJoinedChannel: null,
messages: [],
channels: [],
messageInputValue: '',
userNameInputValue: '',
userIdInputValue: '',
channelNameUpdateValue: '',
settingUpUser: true,
file: null,
messageToUpdate: null,
messageCollection: null,
loading: false,
error: false,
isReactions: false,
currentMessage: {},
});
const { intl } = props;
const APP_ID = 'myappid';
const currentUser = useSelector(getCurrentUser);
const userId = currentUser?.id?.uuid;
const userNickName = currentUser?.attributes?.profile?.displayName;
//need to access state in message received callback
const stateRef = useRef();
stateRef.current = state;
const channelRef = useRef();
useEffect(() => {
const setupUser = async () => {
const sendbirdChat = await SendbirdChat.init({
appId: 'myappid',
localCacheEnabled: true,
modules: [new GroupChannelModule()],
});
await sendbirdChat.connect(userId);
await sendbirdChat.setChannelInvitationPreference(true);
const userUpdateParams = {};
userUpdateParams.nickname = userNickName;
userUpdateParams.userId = userId;
await sendbirdChat.updateCurrentUserInfo(userUpdateParams);
sb = sendbirdChat;
updateState({ ...state, loading: true });
const [channels, error] = await loadChannels(channelHandlers);
if (error) {
return onError(error);
}
updateState({ ...state, channels: channels, loading: false, settingUpUser: false });
};
setupUser();
}, []);
const channelHandlers = {
onChannelsAdded: (context, channels) => {
const updatedChannels = [...channels, ...stateRef.current.channels];
updateState({ ...stateRef.current, channels: updatedChannels, applicationUsers: [] });
},
onChannelsDeleted: (context, channels) => {
const updatedChannels = stateRef.current.channels.filter(channel => {
return !channels.includes(channel.url);
});
updateState({ ...stateRef.current, channels: updatedChannels });
},
onChannelsUpdated: (context, channels) => {
const updatedChannels = stateRef.current.channels.map(channel => {
const updatedChannel = channels.find(
incomingChannel => incomingChannel.url === channel.url
);
if (updatedChannel) {
return updatedChannel;
} else {
return channel;
}
});
updateState({ ...stateRef.current, channels: updatedChannels });
},
};
const messageHandlers = {
onMessagesAdded: (context, channel, messages) => {
const updatedMessages = [...stateRef.current.messages, ...messages];
updateState({ ...stateRef.current, messages: updatedMessages });
},
onMessagesUpdated: (context, channel, messages) => {
const updatedMessages = [...stateRef.current.messages];
for (let i in messages) {
const incomingMessage = messages[i];
const indexOfExisting = stateRef.current.messages.findIndex(message => {
return incomingMessage.reqId === message.reqId;
});
if (indexOfExisting !== -1) {
updatedMessages[indexOfExisting] = incomingMessage;
}
if (!incomingMessage.reqId) {
updatedMessages.push(incomingMessage);
}
}
updateState({ ...stateRef.current, messages: updatedMessages });
},
onMessagesDeleted: (context, channel, messageIds) => {
const updateMessages = stateRef.current.messages.filter(message => {
return !messageIds.includes(message.messageId);
});
updateState({ ...stateRef.current, messages: updateMessages });
},
onChannelUpdated: (context, channel) => {},
onChannelDeleted: (context, channelUrl) => {},
onHugeGapDetected: () => {},
};
const scrollToBottom = (item, smooth) => {
item?.scrollTo({
top: item.scrollHeight,
behavior: smooth,
});
};
useEffect(() => {
scrollToBottom(channelRef.current, 'smooth');
}, [state.currentlyJoinedChannel]);
useEffect(() => {
scrollToBottom(channelRef.current, 'smooth');
}, [state.messages]);
const onError = error => {
updateState({ ...state, error: error.message });
console.log(error, ' - THIS ERROR');
};
const handleJoinChannel = async channelUrl => {
if (state.messageCollection && state.messageCollection.dispose) {
state.messageCollection?.dispose();
}
if (state.currentlyJoinedChannel?.url === channelUrl) {
return null;
}
const { channels } = state;
updateState({ ...state, loading: true });
const channel = channels.find(channel => channel.url === channelUrl);
const onCacheResult = (err, messages) => {
updateState({
...stateRef.current,
currentlyJoinedChannel: channel,
messages: messages.reverse(),
loading: false,
});
};
const onApiResult = (err, messages) => {
updateState({
...stateRef.current,
currentlyJoinedChannel: channel,
messages: messages.reverse(),
loading: false,
});
};
const collection = loadMessages(channel, messageHandlers, onCacheResult, onApiResult);
updateState({ ...state, messageCollection: collection });
};
const handleLeaveChannel = async () => {
const { currentlyJoinedChannel } = state;
await currentlyJoinedChannel.leave();
updateState({ ...state, currentlyJoinedChannel: null });
};
const handleCreateChannel = async (channelName = 'testChannel') => {
const [groupChannel, error] = await createChannel(channelName, state.groupChannelMembers);
if (error) {
return onError(error);
}
};
const handleUpdateChannelMembersList = async () => {
const { currentlyJoinedChannel, groupChannelMembers } = state;
await inviteUsersToChannel(currentlyJoinedChannel, groupChannelMembers);
updateState({ ...state, applicationUsers: [] });
};
const onMessageInputChange = e => {
const messageInputValue = e.currentTarget.value;
updateState({ ...state, messageInputValue });
};
const sendMessage = async () => {
const { messageToUpdate, currentlyJoinedChannel, messages } = state;
if (messageToUpdate) {
const userMessageUpdateParams = {};
userMessageUpdateParams.message = state.messageInputValue;
const updatedMessage = await currentlyJoinedChannel.updateUserMessage(
messageToUpdate.messageId,
userMessageUpdateParams
);
const messageIndex = messages.findIndex(item => item.messageId == messageToUpdate.messageId);
messages[messageIndex] = updatedMessage;
updateState({ ...state, messages: messages, messageInputValue: '', messageToUpdate: null });
} else {
const userMessageParams = {};
userMessageParams.message = state.messageInputValue;
currentlyJoinedChannel
.sendUserMessage(userMessageParams)
.onSucceeded(message => {
updateState({ ...stateRef.current, messageInputValue: '' });
})
.onFailed(error => {
console.log(error);
console.log('failed');
});
}
};
const onFileInputChange = async e => {
if (e.currentTarget.files && e.currentTarget.files.length > 0) {
const { currentlyJoinedChannel, messages } = state;
const fileMessageParams = {};
fileMessageParams.file = e.currentTarget.files[0];
currentlyJoinedChannel
.sendFileMessage(fileMessageParams)
.onSucceeded(message => {
updateState({ ...stateRef.current, messageInputValue: '', file: null });
})
.onFailed(error => {
console.log(error);
console.log('failed');
});
}
};
const handleDeleteMessage = async messageToDelete => {
const { currentlyJoinedChannel } = state;
await deleteMessage(currentlyJoinedChannel, messageToDelete); // Delete
};
const updateMessage = async message => {
updateState({ ...state, messageToUpdate: message, messageInputValue: message.message });
};
const updateMessageReactions = async message => {
const { messages, currentlyJoinedChannel } = state;
const userMessageUpdateParams = {};
const updatedMessage = await currentlyJoinedChannel.updateUserMessage(
message.messageId,
userMessageUpdateParams
);
const messageIndex = messages.findIndex(item => item.messageId === message.messageId);
messages[messageIndex] = updatedMessage;
updateState({ ...state, messages: messages, isReactions: false });
};
const toggleReactions = async message => {
const { isReactions } = state;
updateState({ ...state, isReactions: !isReactions, currentMessage: message });
};
const addMessageReaction = async (message, e) => {
const { currentlyJoinedChannel } = state;
const emojiKey = e.target.innerText;
const reactionEvent = await currentlyJoinedChannel.addReaction(message, emojiKey);
message.applyReactionEvent(reactionEvent);
updateMessageReactions(message);
updateState({ ...state, isReactions: false, currentMessage: {} });
};
const removeMessageReaction = async (message, messageKey) => {
const { currentlyJoinedChannel } = state;
const reactionEvent = await currentlyJoinedChannel.deleteReaction(message, messageKey);
message.applyReactionEvent(reactionEvent);
updateMessageReactions(message);
};
const handleLoadMemberSelectionList = async () => {
updateState({ ...state, currentlyJoinedChannel: null });
const [users, error] = await getAllApplicationUsers();
if (error) {
return onError(error);
}
updateState({
...state,
currentlyJoinedChannel: null,
applicationUsers: users,
groupChannelMembers: [sb.currentUser.userId],
});
};
const handleDeleteChannel = async channelUrl => {
const [channel, error] = await deleteChannel(channelUrl);
if (error) {
return onError(error);
}
};
const addToChannelMembersList = userId => {
const groupChannelMembers = [...state.groupChannelMembers, userId];
updateState({ ...state, groupChannelMembers: groupChannelMembers });
};
if (state.error) {
return <div className="error">{state.error} check console for more information.</div>;
}
return (
<div className={css.chatWrapper}>
<div className={css.channelListsWrapper}>
<ChannelList
channels={state.channels}
handleJoinChannel={handleJoinChannel}
intl={intl}
type={ChatTypes.GROUP_CHAT}
handleDeleteChannel={handleDeleteChannel}
currentlyJoinedChannel={state.currentlyJoinedChannel}
loading={state.loading}
/>
<ChannelList
channels={state.channels}
handleJoinChannel={handleJoinChannel}
handleDeleteChannel={handleDeleteChannel}
intl={intl}
type={ChatTypes.PRIVATE_CHAT}
currentlyJoinedChannel={state.currentlyJoinedChannel}
loading={state.loading}
/>
</div>
<Channel
currentlyJoinedChannel={state.currentlyJoinedChannel}
handleLeaveChannel={handleLeaveChannel}
>
<MessagesList
messages={state.messages}
handleDeleteMessage={handleDeleteMessage}
updateMessage={updateMessage}
addMessageReaction={addMessageReaction}
removeMessageReaction={removeMessageReaction}
toggleReactions={toggleReactions}
isReactions={state.isReactions}
currentMessage={state.currentMessage}
sb={sb}
channelRef={channelRef}
intl={intl}
/>
<MessageInput
value={state.messageInputValue}
onChange={onMessageInputChange}
sendMessage={sendMessage}
fileSelected={state.file}
onFileInputChange={onFileInputChange}
/>
</Channel>
</div>
);
};
const loadChannels = async channelHandlers => {
const groupChannelFilter = new GroupChannelFilter();
groupChannelFilter.includeEmpty = true;
const collection = sb.groupChannel.createGroupChannelCollection({
filter: groupChannelFilter,
order: GroupChannelListOrder.LATEST_LAST_MESSAGE,
});
collection.setGroupChannelCollectionHandler(channelHandlers);
const channels = await collection.loadMore();
return [channels, null];
};
const loadMessages = (channel, messageHandlers, onCacheResult, onApiResult) => {
const messageFilter = new MessageFilter();
const collection = channel.createMessageCollection({
filter: messageFilter,
startingPoint: Date.now(),
limit: 100,
});
collection.setMessageCollectionHandler(messageHandlers);
collection
.initialize(MessageCollectionInitPolicy.CACHE_AND_REPLACE_BY_API)
.onCacheResult(onCacheResult)
.onApiResult(onApiResult);
return collection;
};
const inviteUsersToChannel = async (channel, userIds) => {
await channel.inviteWithUserIds(userIds);
};
const createChannel = async (channelName, userIdsToInvite) => {
try {
const groupChannelParams = {};
groupChannelParams.invitedUserIds = userIdsToInvite;
groupChannelParams.name = channelName;
groupChannelParams.operatorUserIds = userIdsToInvite;
const groupChannel = await sb.groupChannel.createChannel(groupChannelParams);
return [groupChannel, null];
} catch (error) {
return [null, error];
}
};
const deleteChannel = async channelUrl => {
try {
const channel = await sb.groupChannel.getChannel(channelUrl);
await channel.delete();
return [channel, null];
} catch (error) {
return [null, error];
}
};
const deleteMessage = async (currentlyJoinedChannel, messageToDelete) => {
await currentlyJoinedChannel.deleteMessage(messageToDelete);
};
const getAllApplicationUsers = async () => {
try {
const userQuery = sb.createApplicationUserListQuery({ limit: 100 });
const users = await userQuery.next();
return [users, null];
} catch (error) {
return [null, error];
}
};
const TripChat = injectIntl(TripChatComponent);
export default TripChat;
UPD: Also sometimes (rarely) I get error:
Uncaught (in promise) SendbirdError: Invalid parameters.
[UIKit Version]
@sendbird/uikit-react": “^3.4.6”
[Reproduction Steps]
The error occurs while loading the page. Sometimes everything loads and works, but sometimes this error occurs and the application breaks