From fc2d65f2026c0755a2acf7d53d2875b049cd8af1 Mon Sep 17 00:00:00 2001 From: Szymon Szlachtowicz <38212223+Szymx95@users.noreply.github.com> Date: Fri, 28 Jan 2022 14:01:53 +0100 Subject: [PATCH] Refactor channels to use reducer (#207) --- .../ActivityCenter/ActivityMessage.tsx | 5 +- .../src/components/Channels/Channel.tsx | 9 +- .../src/components/Channels/Channels.tsx | 11 +- .../src/components/Chat/ChatCreation.tsx | 5 +- .../src/components/Form/ChannelMenu.tsx | 33 ++-- .../src/contexts/messengerProvider.tsx | 3 +- .../src/hooks/messenger/useGroupChats.ts | 26 +-- .../src/hooks/messenger/useMessenger.ts | 167 ++++++++++++------ packages/status-communities/src/groupChats.ts | 9 +- 9 files changed, 161 insertions(+), 107 deletions(-) diff --git a/packages/react-chat/src/components/ActivityCenter/ActivityMessage.tsx b/packages/react-chat/src/components/ActivityCenter/ActivityMessage.tsx index 368cc26d..666cc64e 100644 --- a/packages/react-chat/src/components/ActivityCenter/ActivityMessage.tsx +++ b/packages/react-chat/src/components/ActivityCenter/ActivityMessage.tsx @@ -47,10 +47,11 @@ export function ActivityMessage({ activity, setShowActivityCenter, }: ActivityMessageProps) { - const { contacts, setActiveChannel } = useMessengerContext(); + const { contacts, channelsDispatch } = useMessengerContext(); const { setModal } = useModal(ProfileModalName); const showChannel = () => { - activity.channel && setActiveChannel(activity.channel), + activity.channel && + channelsDispatch({ type: "ChangeActive", payload: activity.channel.id }), setShowActivityCenter(false); }; diff --git a/packages/react-chat/src/components/Channels/Channel.tsx b/packages/react-chat/src/components/Channels/Channel.tsx index ebe1a611..6aa60d91 100644 --- a/packages/react-chat/src/components/Channels/Channel.tsx +++ b/packages/react-chat/src/components/Channels/Channel.tsx @@ -59,6 +59,7 @@ export function Channel({ setEditGroup, }: ChannelProps) { const narrow = useNarrow(); + const { channelsDispatch } = useMessengerContext(); return ( {channel?.isMuted && activeView && !narrow && ( - (channel.isMuted = !channel.isMuted)}> + + channelsDispatch({ type: "ToggleMuted", payload: channel.id }) + } + > @@ -90,7 +95,7 @@ export function Channel({ )} - {!activeView && !!mention && mention > 0 && !channel?.isMuted && ( + {!activeView && !!mention && !channel?.isMuted && ( {mention} )} {channel?.isMuted && !activeView && } diff --git a/packages/react-chat/src/components/Channels/Channels.tsx b/packages/react-chat/src/components/Channels/Channels.tsx index d05d6039..e0e5428d 100644 --- a/packages/react-chat/src/components/Channels/Channels.tsx +++ b/packages/react-chat/src/components/Channels/Channels.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useMemo } from "react"; import styled from "styled-components"; import { ChatState, useChatState } from "../../contexts/chatStateProvider"; @@ -23,12 +23,15 @@ function GenerateChannels({ onCommunityClick, setEditGroup, }: GenerateChannelsProps) { - const { mentions, notifications, activeChannel, setActiveChannel, channels } = + const { mentions, notifications, activeChannel, channelsDispatch, channels } = useMessengerContext(); + + const channelList = useMemo(() => Object.values(channels), [channels]); + const setChatState = useChatState()[1]; return ( <> - {Object.values(channels) + {channelList .filter((channel) => channel.type === type) .map((channel) => ( 0} mention={mentions?.[channel.id]} onClick={() => { - setActiveChannel(channel); + channelsDispatch({ type: "ChangeActive", payload: channel.id }); if (onCommunityClick) { onCommunityClick(); } diff --git a/packages/react-chat/src/components/Chat/ChatCreation.tsx b/packages/react-chat/src/components/Chat/ChatCreation.tsx index 85843326..e80e16d1 100644 --- a/packages/react-chat/src/components/Chat/ChatCreation.tsx +++ b/packages/react-chat/src/components/Chat/ChatCreation.tsx @@ -34,8 +34,7 @@ export function ChatCreation({ (member) => member?.customName ?? member.trueName ) ?? [] ); - const { contacts, createGroupChat, addMembers, setChannel } = - useMessengerContext(); + const { contacts, createGroupChat, addMembers } = useMessengerContext(); const setChatState = useChatState()[1]; const addMember = useCallback( @@ -71,7 +70,7 @@ export function ChatCreation({ setChatState(ChatState.ChatBody); } }, - [identity, createGroupChat, setChannel] + [identity, createGroupChat] ); return ( diff --git a/packages/react-chat/src/components/Form/ChannelMenu.tsx b/packages/react-chat/src/components/Form/ChannelMenu.tsx index 12c76573..023feadb 100644 --- a/packages/react-chat/src/components/Form/ChannelMenu.tsx +++ b/packages/react-chat/src/components/Form/ChannelMenu.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from "react"; +import React, { useMemo, useState } from "react"; import styled from "styled-components"; import { useMessengerContext } from "../../contexts/messengerProvider"; @@ -41,20 +41,12 @@ export const ChannelMenu = ({ className, }: ChannelMenuProps) => { const narrow = useNarrow(); - const { clearNotifications } = useMessengerContext(); + const { clearNotifications, channelsDispatch } = useMessengerContext(); const { setModal } = useModal(EditModalName); const { setModal: setLeavingModal } = useModal(LeavingModalName); const { setModal: setProfileModal } = useModal(ProfileModalName); const [showSubmenu, setShowSubmenu] = useState(false); - const muting = channel.isMuted; - const [isMuted, setIsMuted] = useState(muting); - - useEffect(() => { - if (isMuted) channel.isMuted = true; - if (!isMuted) channel.isMuted = false; - }, [isMuted]); - const { showMenu, setShowMenu: setShowSideMenu } = useContextMenu( channel.id + "contextMenu" ); @@ -113,26 +105,31 @@ export const ChannelMenu = ({ { - if (isMuted) { - setIsMuted(false); + if (channel.isMuted) { + channelsDispatch({ type: "ToggleMuted", payload: channel.id }); setShowMenu(false); } }} onMouseEnter={() => { - if (!isMuted) setShowSubmenu(true); + if (!channel.isMuted) setShowSubmenu(true); }} onMouseLeave={() => { - if (!isMuted) setShowSubmenu(false); + if (!channel.isMuted) setShowSubmenu(false); }} > - {!isMuted && } + {!channel.isMuted && } - {(isMuted ? "Unmute" : "Mute") + + {(channel.isMuted ? "Unmute" : "Mute") + (channel.type === "group" ? " Group" : " Chat")} - {!isMuted && showSubmenu && ( - + {!channel.isMuted && showSubmenu && ( + + channelsDispatch({ type: "ToggleMuted", payload: channel.id }) + } + className={className} + /> )} clearNotifications(channel.id)}> diff --git a/packages/react-chat/src/contexts/messengerProvider.tsx b/packages/react-chat/src/contexts/messengerProvider.tsx index 0eeefaf8..b0300824 100644 --- a/packages/react-chat/src/contexts/messengerProvider.tsx +++ b/packages/react-chat/src/contexts/messengerProvider.tsx @@ -20,9 +20,8 @@ const MessengerContext = createContext({ setContacts: () => undefined, activeChannel: undefined, channels: {}, - setChannel: () => undefined, + channelsDispatch: () => undefined, removeChannel: () => undefined, - setActiveChannel: () => undefined, createGroupChat: () => undefined, changeGroupChatName: () => undefined, addMembers: () => undefined, diff --git a/packages/react-chat/src/hooks/messenger/useGroupChats.ts b/packages/react-chat/src/hooks/messenger/useGroupChats.ts index 54373ac3..ffdb8729 100644 --- a/packages/react-chat/src/hooks/messenger/useGroupChats.ts +++ b/packages/react-chat/src/hooks/messenger/useGroupChats.ts @@ -7,11 +7,13 @@ import { } from "@waku/status-communities/dist/cjs"; import { useCallback, useMemo } from "react"; -import { ChannelData, ChannelsData } from "../../models/ChannelData"; +import { ChannelData } from "../../models/ChannelData"; import { ChatMessage } from "../../models/ChatMessage"; import { Contact } from "../../models/Contact"; import { uintToImgUrl } from "../../utils"; +import { ChannelAction } from "./useMessenger"; + const contactFromId = (member: string): Contact => { return { blocked: false, @@ -25,10 +27,8 @@ const contactFromId = (member: string): Contact => { export function useGroupChats( messenger: Messenger | undefined, identity: Identity | undefined, - setChannels: React.Dispatch>, - setActiveChannel: (channel: ChannelData) => void, - addChatMessage: (newMessage: ChatMessage | undefined, id: string) => void, - channels: ChannelsData + dispatch: (action: ChannelAction) => void, + addChatMessage: (newMessage: ChatMessage | undefined, id: string) => void ) { const groupChat = useMemo(() => { if (messenger && identity) { @@ -51,20 +51,10 @@ export function useGroupChats( type: "dm", description: `Chatkey: ${chat.members[0].id}`, }; - setChannels((prev) => { - return { ...prev, [channel.id]: channel }; - }); + dispatch({ type: "AddChannel", payload: channel }); }; const removeChat = (chat: GroupChat) => { - setChannels((prev) => { - delete prev[chat.chatId]; - return prev; - }); - setActiveChannel({ - id: "", - name: "", - type: "channel", - } as ChannelData); + dispatch({ type: "RemoveChannel", payload: chat.chatId }); }; const handleMessage = (msg: StatusChatMessage, sender: string) => { let image: string | undefined = undefined; @@ -116,7 +106,7 @@ export function useGroupChats( groupChat.quitChat(channelId); } }, - [channels, groupChat] + [groupChat] ); const addMembers = useCallback( diff --git a/packages/react-chat/src/hooks/messenger/useMessenger.ts b/packages/react-chat/src/hooks/messenger/useMessenger.ts index e392269a..eef2c9b7 100644 --- a/packages/react-chat/src/hooks/messenger/useMessenger.ts +++ b/packages/react-chat/src/hooks/messenger/useMessenger.ts @@ -6,7 +6,7 @@ import { Identity, Messenger, } from "@waku/status-communities/dist/cjs"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useReducer, useState } from "react"; import { useConfig } from "../../contexts/configProvider"; import { ChannelData, ChannelsData } from "../../models/ChannelData"; @@ -41,10 +41,9 @@ export type MessengerType = { contacts: Contacts; setContacts: React.Dispatch>; channels: ChannelsData; - setChannel: (channel: ChannelData) => void; + channelsDispatch: (action: ChannelAction) => void; removeChannel: (channelId: string) => void; activeChannel: ChannelData | undefined; - setActiveChannel: (channel: ChannelData) => void; createGroupChat: (members: string[]) => void; changeGroupChatName: (name: string, chatId: string) => void; addMembers: (members: string[], chatId: string) => void; @@ -101,17 +100,84 @@ function useCreateCommunity( return { community, communityData }; } +export type ChannelsState = { + channels: ChannelsData; + activeChannel: ChannelData; +}; + +export type ChannelAction = + | { type: "AddChannel"; payload: ChannelData } + | { type: "UpdateActive"; payload: ChannelData } + | { type: "ChangeActive"; payload: string } + | { type: "ToggleMuted"; payload: string } + | { type: "RemoveChannel"; payload: string }; + +function channelReducer( + state: ChannelsState, + action: ChannelAction +): ChannelsState { + switch (action.type) { + case "AddChannel": { + const channels = { + ...state.channels, + [action.payload.id]: action.payload, + }; + return { channels, activeChannel: action.payload }; + } + case "UpdateActive": { + const activeChannel = state.activeChannel; + if (activeChannel) { + return { + channels: { ...state.channels, [activeChannel.id]: action.payload }, + activeChannel: action.payload, + }; + } + return state; + } + case "ChangeActive": { + const newActive = state.channels[action.payload]; + if (newActive) { + return { ...state, activeChannel: newActive }; + } + return state; + } + case "ToggleMuted": { + const channel = state.channels[action.payload]; + if (channel) { + const updatedChannel: ChannelData = { + ...channel, + isMuted: !channel.isMuted, + }; + return { + channels: { ...state.channels, [channel.id]: updatedChannel }, + activeChannel: updatedChannel, + }; + } + return state; + } + case "RemoveChannel": { + const channelsCopy = { ...state.channels }; + delete channelsCopy[action.payload]; + let newActive = { id: "", name: "", type: "channel" } as ChannelData; + if (Object.values(channelsCopy).length > 0) { + newActive = Object.values(channelsCopy)[0]; + } + return { channels: channelsCopy, activeChannel: newActive }; + } + default: + throw new Error(); + } +} + export function useMessenger( communityKey: string, identity: Identity | undefined, newNickname: string | undefined ) { - const [activeChannel, setActiveChannel] = useState({ - id: "", - name: "", - description: "", - type: "channel", - }); + const [channelsState, channelsDispatch] = useReducer(channelReducer, { + channels: {}, + activeChannel: { id: "", name: "", type: "channel" }, + } as ChannelsState); const messenger = useCreateMessenger(identity); @@ -129,7 +195,7 @@ export function useMessenger( messages, mentions, clearMentions, - } = useMessages(activeChannel.id, identity, contactsClass); + } = useMessages(channelsState?.activeChannel?.id, identity, contactsClass); const { community, communityData } = useCreateCommunity( messenger, @@ -138,37 +204,34 @@ export function useMessenger( contactsClass ); - const [channels, setChannels] = useState({}); - - const setChannel = useCallback((channel: ChannelData) => { - setChannels((prev) => { - return { ...prev, [channel.id]: channel }; - }); - setActiveChannel(channel); - }, []); - useEffect(() => { if (community?.chats) { for (const chat of community.chats.values()) { - setChannel({ - id: chat.id, - name: chat.communityChat?.identity?.displayName ?? "", - description: chat.communityChat?.identity?.description ?? "", - type: "channel", + channelsDispatch({ + type: "AddChannel", + payload: { + id: chat.id, + name: chat.communityChat?.identity?.displayName ?? "", + description: chat.communityChat?.identity?.description ?? "", + type: "channel", + }, }); } } }, [community]); useEffect(() => { - Object.values(channels) + Object.values(channelsState.channels) .filter((channel) => channel.type === "dm") .forEach((channel) => { const contact = contacts?.[channel?.members?.[0]?.id ?? ""]; if (contact && channel.name !== (contact?.customName ?? channel.name)) { - setChannel({ - ...channel, - name: contact?.customName ?? channel.name, + channelsDispatch({ + type: "AddChannel", + payload: { + ...channel, + name: contact?.customName ?? channel.name, + }, }); } }); @@ -180,17 +243,10 @@ export function useMessenger( createGroupChat, changeGroupChatName, addMembers, - } = useGroupChats( - messenger, - identity, - setChannels, - setActiveChannel, - addChatMessage, - channels - ); + } = useGroupChats(messenger, identity, channelsDispatch, addChatMessage); const { loadPrevDay, loadingMessages } = useLoadPrevDay( - activeChannel.id, + channelsState.activeChannel.id, messenger, groupChat ); @@ -220,28 +276,36 @@ export function useMessenger( }; } if (content) { - if (activeChannel.type === "group") { - await groupChat?.sendMessage(activeChannel.id, content, responseTo); + if (channelsState.activeChannel.type === "group") { + await groupChat?.sendMessage( + channelsState.activeChannel.id, + content, + responseTo + ); } else { - await messenger?.sendMessage(activeChannel.id, content, responseTo); + await messenger?.sendMessage( + channelsState.activeChannel.id, + content, + responseTo + ); } } }, - [messenger, groupChat, activeChannel] + [messenger, groupChat, channelsState.activeChannel] ); useEffect(() => { - if (activeChannel) { - if (notifications[activeChannel.id] > 0) { - clearNotifications(activeChannel.id); - clearMentions(activeChannel.id); + if (channelsState.activeChannel) { + if (notifications[channelsState.activeChannel.id] > 0) { + clearNotifications(channelsState.activeChannel.id); + clearMentions(channelsState.activeChannel.id); } } - }, [notifications, activeChannel]); + }, [notifications, channelsState]); const loadingMessenger = useMemo(() => { - return !communityData || !messenger || !activeChannel; - }, [communityData, messenger, activeChannel]); + return !communityData || !messenger || !channelsState.activeChannel.id; + }, [communityData, messenger, channelsState]); return { messenger, @@ -255,11 +319,10 @@ export function useMessenger( communityData, contacts, setContacts, - channels, - setChannel, + channels: channelsState.channels, + channelsDispatch, removeChannel, - activeChannel, - setActiveChannel, + activeChannel: channelsState.activeChannel, mentions, clearMentions, createGroupChat, diff --git a/packages/status-communities/src/groupChats.ts b/packages/status-communities/src/groupChats.ts index 60699517..cd397e5a 100644 --- a/packages/status-communities/src/groupChats.ts +++ b/packages/status-communities/src/groupChats.ts @@ -287,12 +287,9 @@ export class GroupChats { messages.sort((a, b) => (a?.timestamp?.getTime() ?? 0) < (b?.timestamp?.getTime() ?? 0) ? -1 : 1 ); - - await Promise.all( - messages.map( - async (message) => await this.decodeUpdateMessage(message, false) - ) - ); + for (let i = 0; i < messages.length; i++) { + await this.decodeUpdateMessage(messages[i], false); + } this.waku.relay.addObserver( (message) => this.decodeUpdateMessage(message, true), [topic]