1- import { useContext , useMemo } from 'react' ;
1+ import { useContext , useMemo , useCallback } from 'react' ;
22import type { GroupChannel } from '@sendbird/chat/groupChannel' ;
33import type { SendbirdError } from '@sendbird/chat' ;
44import { useSyncExternalStore } from 'use-sync-external-store/shim' ;
@@ -58,24 +58,21 @@ export const useGroupChannel = () => {
5858 const { markAsReadScheduler } = config ;
5959 const state : GroupChannelState = useSyncExternalStore ( store . subscribe , store . getState ) ;
6060
61- const flagActions = {
62- setAnimatedMessageId : ( messageId : number | null ) => {
63- store . setState ( state => ( { ...state , animatedMessageId : messageId } ) ) ;
64- } ,
61+ const setAnimatedMessageId = useCallback ( ( messageId : number | null ) => {
62+ store . setState ( state => ( { ...state , animatedMessageId : messageId } ) ) ;
63+ } , [ ] ) ;
6564
66- setIsScrollBottomReached : ( isReached : boolean ) => {
67- store . setState ( state => ( { ...state , isScrollBottomReached : isReached } ) ) ;
68- } ,
69- } ;
65+ const setIsScrollBottomReached = useCallback ( ( isReached : boolean ) => {
66+ store . setState ( state => ( { ...state , isScrollBottomReached : isReached } ) ) ;
67+ } , [ ] ) ;
7068
71- const scrollToBottom = async ( animated ?: boolean ) => {
69+ const scrollToBottom = useCallback ( async ( animated ?: boolean ) => {
7270 if ( ! state . scrollRef . current ) return ;
71+ setAnimatedMessageId ( null ) ;
72+ setIsScrollBottomReached ( true ) ;
73+
7374 // wait a bit for scroll ref to be updated
7475 await delay ( ) ;
75-
76- flagActions . setAnimatedMessageId ( null ) ;
77- flagActions . setIsScrollBottomReached ( true ) ;
78-
7976 if ( config . isOnline && state . hasNext ( ) ) {
8077 await state . resetWithStartingPoint ( Number . MAX_SAFE_INTEGER ) ;
8178 }
@@ -87,109 +84,127 @@ export const useGroupChannel = () => {
8784 markAsReadScheduler . push ( state . currentChannel ) ;
8885 }
8986 }
90- } ;
87+ } , [ state . scrollRef . current , config . isOnline , markAsReadScheduler ] ) ;
88+
89+ const scrollToMessage = useCallback ( async (
90+ createdAt : number ,
91+ messageId : number ,
92+ messageFocusAnimated ?: boolean ,
93+ scrollAnimated ?: boolean ,
94+ ) => {
95+ const element = state . scrollRef . current ;
96+ const parentNode = element ?. parentNode as HTMLDivElement ;
97+ const clickHandler = {
98+ activate ( ) {
99+ if ( ! element || ! parentNode ) return ;
100+ element . style . pointerEvents = 'auto' ;
101+ parentNode . style . cursor = 'auto' ;
102+ } ,
103+ deactivate ( ) {
104+ if ( ! element || ! parentNode ) return ;
105+ element . style . pointerEvents = 'none' ;
106+ parentNode . style . cursor = 'wait' ;
107+ } ,
108+ } ;
109+
110+ clickHandler . deactivate ( ) ;
111+
112+ setAnimatedMessageId ( null ) ;
113+ const message = state . messages . find (
114+ ( it ) => it . messageId === messageId || it . createdAt === createdAt ,
115+ ) ;
116+
117+ if ( message ) {
118+ const topOffset = getMessageTopOffset ( message . createdAt ) ;
119+ if ( topOffset ) state . scrollPubSub . publish ( 'scroll' , { top : topOffset , animated : scrollAnimated } ) ;
120+ if ( messageFocusAnimated ?? true ) setAnimatedMessageId ( messageId ) ;
121+ } else {
122+ await state . resetWithStartingPoint ( createdAt ) ;
123+ setTimeout ( ( ) => {
124+ const topOffset = getMessageTopOffset ( createdAt ) ;
125+ if ( topOffset ) {
126+ state . scrollPubSub . publish ( 'scroll' , {
127+ top : topOffset ,
128+ lazy : false ,
129+ animated : scrollAnimated ,
130+ } ) ;
131+ }
132+ if ( messageFocusAnimated ?? true ) setAnimatedMessageId ( messageId ) ;
133+ } ) ;
134+ }
135+ clickHandler . activate ( ) ;
136+ } , [ setAnimatedMessageId , state . scrollRef . current , state . messages ?. map ( it => it ?. messageId ) ] ) ;
137+
138+ const toggleReaction = useCallback ( ( message : SendableMessageType , emojiKey : string , isReacted : boolean ) => {
139+ if ( ! state . currentChannel ) return ;
140+ if ( isReacted ) {
141+ state . currentChannel . deleteReaction ( message , emojiKey )
142+ . catch ( error => {
143+ config . logger ?. warning ( 'Failed to delete reaction:' , error ) ;
144+ } ) ;
145+ } else {
146+ state . currentChannel . addReaction ( message , emojiKey )
147+ . catch ( error => {
148+ config . logger ?. warning ( 'Failed to add reaction:' , error ) ;
149+ } ) ;
150+ }
151+ } , [ state . currentChannel ?. deleteReaction , state . currentChannel ?. addReaction ] ) ;
152+
91153 const messageActions = useMessageActions ( {
92154 ...state ,
93155 scrollToBottom,
94156 } ) ;
95157
96- const actions : GroupChannelActions = useMemo ( ( ) => ( {
97- setCurrentChannel : ( channel : GroupChannel ) => {
98- store . setState ( state => ( {
99- ...state ,
100- currentChannel : channel ,
101- fetchChannelError : null ,
102- quoteMessage : null ,
103- animatedMessageId : null ,
104- nicknamesMap : new Map (
105- channel . members . map ( ( { userId, nickname } ) => [ userId , nickname ] ) ,
106- ) ,
107- } ) ) ;
108- } ,
109-
110- handleChannelError : ( error : SendbirdError ) => {
111- store . setState ( state => ( {
112- ...state ,
113- currentChannel : null ,
114- fetchChannelError : error ,
115- quoteMessage : null ,
116- animatedMessageId : null ,
117- } ) ) ;
118- } ,
119-
120- setQuoteMessage : ( message : SendableMessageType | null ) => {
121- store . setState ( state => ( { ...state , quoteMessage : message } ) ) ;
122- } ,
123-
158+ const setCurrentChannel = useCallback ( ( channel : GroupChannel ) => {
159+ store . setState ( state => ( {
160+ ...state ,
161+ currentChannel : channel ,
162+ fetchChannelError : null ,
163+ quoteMessage : null ,
164+ animatedMessageId : null ,
165+ nicknamesMap : new Map (
166+ channel . members . map ( ( { userId, nickname } ) => [ userId , nickname ] ) ,
167+ ) ,
168+ } ) ) ;
169+ } , [ ] ) ;
170+
171+ const handleChannelError = useCallback ( ( error : SendbirdError ) => {
172+ store . setState ( state => ( {
173+ ...state ,
174+ currentChannel : null ,
175+ fetchChannelError : error ,
176+ quoteMessage : null ,
177+ animatedMessageId : null ,
178+ } ) ) ;
179+ } , [ ] ) ;
180+
181+ const setQuoteMessage = useCallback ( ( message : SendableMessageType | null ) => {
182+ store . setState ( state => ( { ...state , quoteMessage : message } ) ) ;
183+ } , [ ] ) ;
184+
185+ const actions : GroupChannelActions = useMemo ( ( ) => {
186+ return {
187+ setCurrentChannel,
188+ handleChannelError,
189+ setQuoteMessage,
190+ scrollToBottom,
191+ scrollToMessage,
192+ toggleReaction,
193+ setAnimatedMessageId,
194+ setIsScrollBottomReached,
195+ ...messageActions ,
196+ } ;
197+ } , [
198+ setCurrentChannel ,
199+ handleChannelError ,
200+ setQuoteMessage ,
124201 scrollToBottom ,
125- scrollToMessage : async (
126- createdAt : number ,
127- messageId : number ,
128- messageFocusAnimated ?: boolean ,
129- scrollAnimated ?: boolean ,
130- ) => {
131- const element = state . scrollRef . current ;
132- const parentNode = element ?. parentNode as HTMLDivElement ;
133- const clickHandler = {
134- activate ( ) {
135- if ( ! element || ! parentNode ) return ;
136- element . style . pointerEvents = 'auto' ;
137- parentNode . style . cursor = 'auto' ;
138- } ,
139- deactivate ( ) {
140- if ( ! element || ! parentNode ) return ;
141- element . style . pointerEvents = 'none' ;
142- parentNode . style . cursor = 'wait' ;
143- } ,
144- } ;
145-
146- clickHandler . deactivate ( ) ;
147-
148- flagActions . setAnimatedMessageId ( null ) ;
149- const message = state . messages . find (
150- ( it ) => it . messageId === messageId || it . createdAt === createdAt ,
151- ) ;
152-
153- if ( message ) {
154- const topOffset = getMessageTopOffset ( message . createdAt ) ;
155- if ( topOffset ) state . scrollPubSub . publish ( 'scroll' , { top : topOffset , animated : scrollAnimated } ) ;
156- if ( messageFocusAnimated ?? true ) flagActions . setAnimatedMessageId ( messageId ) ;
157- } else {
158- await state . resetWithStartingPoint ( createdAt ) ;
159- setTimeout ( ( ) => {
160- const topOffset = getMessageTopOffset ( createdAt ) ;
161- if ( topOffset ) {
162- state . scrollPubSub . publish ( 'scroll' , {
163- top : topOffset ,
164- lazy : false ,
165- animated : scrollAnimated ,
166- } ) ;
167- }
168- if ( messageFocusAnimated ?? true ) flagActions . setAnimatedMessageId ( messageId ) ;
169- } ) ;
170- }
171-
172- clickHandler . activate ( ) ;
173- } ,
174-
175- toggleReaction : ( message : SendableMessageType , emojiKey : string , isReacted : boolean ) => {
176- if ( ! state . currentChannel ) return ;
177-
178- if ( isReacted ) {
179- state . currentChannel . deleteReaction ( message , emojiKey )
180- . catch ( error => {
181- config . logger ?. warning ( 'Failed to delete reaction:' , error ) ;
182- } ) ;
183- } else {
184- state . currentChannel . addReaction ( message , emojiKey )
185- . catch ( error => {
186- config . logger ?. warning ( 'Failed to add reaction:' , error ) ;
187- } ) ;
188- }
189- } ,
190- ...flagActions ,
191- ...messageActions ,
192- } ) , [ store , state , config . isOnline , markAsReadScheduler ] ) ;
202+ scrollToMessage ,
203+ toggleReaction ,
204+ setAnimatedMessageId ,
205+ setIsScrollBottomReached ,
206+ messageActions ,
207+ ] ) ;
193208
194209 return { state, actions } ;
195210} ;
0 commit comments