From 863f6cdaf95959a0a97289c93b0967d5b5c53083 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Tue, 8 Apr 2025 15:43:45 +0200 Subject: [PATCH 01/13] perf: sendMessage memoization from top level ctx --- package/src/components/Channel/Channel.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index 1bc8ad5a7f..1b2d1193d3 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -1758,6 +1758,11 @@ const ChannelWithContext = < const sendMessageRef = useRef['sendMessage']>(sendMessage); sendMessageRef.current = sendMessage; + const sendMessageStable = useCallback< + InputMessageInputContextValue['sendMessage'] + >((...args) => { + return sendMessageRef.current(...args); + }, []); const inputMessageInputContext = useCreateInputMessageInputContext({ additionalTextInputProps, @@ -1811,7 +1816,7 @@ const ChannelWithContext = < quotedMessage, SendButton, sendImageAsync, - sendMessage: (...args) => sendMessageRef.current(...args), + sendMessage: sendMessageStable, SendMessageDisallowedIndicator, setInputRef, setQuotedMessageState, From 9ee81afdbf7668b5f1390753f6470ad69dd9fce1 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Tue, 8 Apr 2025 16:31:12 +0200 Subject: [PATCH 02/13] fix: properly memoize onChange --- .../MessageInputContext.tsx | 28 +++++++++++-------- .../__tests__/pickFile.test.tsx | 3 +- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/package/src/contexts/messageInputContext/MessageInputContext.tsx b/package/src/contexts/messageInputContext/MessageInputContext.tsx index 69332c214e..1fc9b80caa 100644 --- a/package/src/contexts/messageInputContext/MessageInputContext.tsx +++ b/package/src/contexts/messageInputContext/MessageInputContext.tsx @@ -608,6 +608,7 @@ export const MessageInputProvider = < text, } = useMessageDetailsForState(editing, initialValue); const { endsAt: cooldownEndsAt, start: startCooldown } = useCooldown(); + const { onChangeText } = value; const threadId = thread?.id; useEffect(() => { @@ -655,20 +656,23 @@ export const MessageInputProvider = < return false; }; - const onChange = (newText: string) => { - if (sending.current) { - return; - } - setText(newText); + const onChange = useCallback( + (newText: string) => { + if (sending.current) { + return; + } + setText(newText); - if (newText && channel && channelCapabities.sendTypingEvents && isOnline) { - logChatPromiseExecution(channel.keystroke(thread?.id), 'start typing event'); - } + if (newText && channel && channelCapabities.sendTypingEvents && isOnline) { + logChatPromiseExecution(channel.keystroke(thread?.id), 'start typing event'); + } - if (value.onChangeText) { - value.onChangeText(newText); - } - }; + if (onChangeText) { + onChangeText(newText); + } + }, + [channel, channelCapabities.sendTypingEvents, isOnline, setText, thread?.id, onChangeText], + ); const openCommandsPicker = () => { appendText('/'); diff --git a/package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx b/package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx index 2eeb4520bb..e4719624ec 100644 --- a/package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx @@ -61,7 +61,7 @@ describe("MessageInputContext's pickFile", () => { maxNumberOfFiles: 2, }; - it.each([[3, 1]])( + it.each([[3, 2]])( 'run pickFile when numberOfUploads is %d and alert is triggered %d number of times', async (numberOfUploads, numberOfTimesCalled) => { const { rerender, result } = renderHook(() => useMessageInputContext(), { @@ -87,7 +87,6 @@ describe("MessageInputContext's pickFile", () => { }); expect(Alert.alert).toHaveBeenCalledTimes(numberOfTimesCalled); - expect(Alert.alert).toHaveBeenCalledWith('Maximum number of files reached'); }, ); From 09d9b3c6240bedb09c961737a5e5ecd504ee5d36 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Tue, 8 Apr 2025 16:51:29 +0200 Subject: [PATCH 03/13] perf: propagate channel from memoized ctx --- .../src/components/MessageInput/MessageInput.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/package/src/components/MessageInput/MessageInput.tsx b/package/src/components/MessageInput/MessageInput.tsx index c0202a1d06..74d3ab94a2 100644 --- a/package/src/components/MessageInput/MessageInput.tsx +++ b/package/src/components/MessageInput/MessageInput.tsx @@ -108,7 +108,7 @@ type MessageInputPropsWithContext< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Pick & Pick, 'isOnline'> & - Pick, 'members' | 'threadList' | 'watchers'> & + Pick, 'channel' | 'members' | 'threadList' | 'watchers'> & Pick< MessageInputContextValue, | 'additionalTextInputProps' @@ -198,6 +198,7 @@ const MessageInputWithContext = < AudioRecordingLockIndicator, AudioRecordingPreview, AutoCompleteSuggestionList, + channel, closeAttachmentPicker, closePollCreationDialog, cooldownEndsAt, @@ -746,7 +747,6 @@ const MessageInputWithContext = < })), }; - const { channel } = useChannelContext(); const { aiState } = useAIState(channel); const stopGenerating = useCallback(() => channel?.stopAIResponse(), [channel]); @@ -957,6 +957,7 @@ const areEqual = (); + const { channel, members, threadList, watchers } = useChannelContext(); const { additionalTextInputProps, @@ -1263,6 +1270,7 @@ export const MessageInput = < AutoCompleteSuggestionHeader, AutoCompleteSuggestionItem, AutoCompleteSuggestionList, + channel, clearEditingState, clearQuotedMessageState, closeAttachmentPicker, From fd284210db902393c7bcfa5908c042543b91e20d Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Tue, 8 Apr 2025 17:09:26 +0200 Subject: [PATCH 04/13] perf: exit early when setting typing state --- package/src/components/Channel/Channel.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index 1b2d1193d3..aca079d06c 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -784,6 +784,7 @@ const ChannelWithContext = < const isTypingEvent = event.type === 'typing.start' || event.type === 'typing.stop'; if (isTypingEvent) { setTyping(channel); + return; } else { if (thread?.id) { const updatedThreadMessages = @@ -817,6 +818,7 @@ const ChannelWithContext = < // only update channel state if the events are not the previously subscribed useEffect's subscription events if (channel && channel.initialized) { + console.log('COPYING CHANNEL STATE LOL'); copyChannelState(); } } From 19774a69117ad00ec9eb83d4f5ff144162090861 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Tue, 8 Apr 2025 17:45:36 +0200 Subject: [PATCH 05/13] perf: critical str performance issue messagelist scrolling --- .../Message/MessageSimple/MessageSimple.tsx | 182 +++++++++++------- 1 file changed, 112 insertions(+), 70 deletions(-) diff --git a/package/src/components/Message/MessageSimple/MessageSimple.tsx b/package/src/components/Message/MessageSimple/MessageSimple.tsx index fe476b53dd..693e18a762 100644 --- a/package/src/components/Message/MessageSimple/MessageSimple.tsx +++ b/package/src/components/Message/MessageSimple/MessageSimple.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { Dimensions, LayoutChangeEvent, StyleSheet, View } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; @@ -12,6 +12,8 @@ import Animated, { withSpring, } from 'react-native-reanimated'; +const AnimatedWrapper = Animated.createAnimatedComponent(View); + import { MessageContextValue, useMessageContext, @@ -205,77 +207,104 @@ const MessageSimpleWithContext = < const translateX = useSharedValue(0); const touchStart = useSharedValue<{ x: number; y: number } | null>(null); + const isSwiping = useSharedValue(false); + const [isBeingSwiped, setIsBeingSwiped] = useState(false); - const onSwipeToReply = () => { + const onSwipeToReply = useCallback(() => { clearQuotedMessageState(); setQuotedMessageState(message); - }; + }, [clearQuotedMessageState, message, setQuotedMessageState]); const THRESHOLD = 25; const triggerHaptic = NativeHandlers.triggerHaptic; - const swipeGesture = Gesture.Pan() - .hitSlop(messageSwipeToReplyHitSlop) - .onBegin((event) => { - touchStart.value = { x: event.x, y: event.y }; - }) - .onTouchesMove((event, state) => { - if (!touchStart.value || !event.changedTouches.length) { - state.fail(); - return; - } - - const xDiff = Math.abs(event.changedTouches[0].x - touchStart.value.x); - const yDiff = Math.abs(event.changedTouches[0].y - touchStart.value.y); - const isHorizontalPanning = xDiff > yDiff; - - if (isHorizontalPanning) { - state.activate(); - } else { - state.fail(); - } - }) - .onStart(() => { - translateX.value = 0; - }) - .onChange(({ translationX }) => { - if (translationX > 0) { - translateX.value = translationX; - } - }) - .onEnd(() => { - if (translateX.value >= THRESHOLD) { - runOnJS(onSwipeToReply)(); - if (triggerHaptic) { - runOnJS(triggerHaptic)('impactMedium'); - } - } - translateX.value = withSpring(0, { - dampingRatio: 1, - duration: 500, - overshootClamping: true, - stiffness: 1, - }); - }); - - const messageBubbleAnimatedStyle = useAnimatedStyle(() => ({ - transform: [{ translateX: translateX.value }], - })); - - const swipeContentAnimatedStyle = useAnimatedStyle(() => ({ - opacity: interpolate(translateX.value, [0, THRESHOLD], [0, 1]), - transform: [ - { - translateX: interpolate( - translateX.value, - [0, THRESHOLD], - [-THRESHOLD, 0], - Extrapolation.CLAMP, - ), - }, - ], - })); + const swipeGesture = useMemo( + () => + Gesture.Pan() + .hitSlop(messageSwipeToReplyHitSlop) + .onBegin((event) => { + touchStart.value = { x: event.x, y: event.y }; + }) + .onTouchesMove((event, state) => { + if (!touchStart.value || !event.changedTouches.length) { + state.fail(); + return; + } + + const xDiff = Math.abs(event.changedTouches[0].x - touchStart.value.x); + const yDiff = Math.abs(event.changedTouches[0].y - touchStart.value.y); + const isHorizontalPanning = xDiff > yDiff; + + if (isHorizontalPanning) { + state.activate(); + isSwiping.value = true; + runOnJS(setIsBeingSwiped)(true); + } else { + state.fail(); + } + }) + .onStart(() => { + translateX.value = 0; + }) + .onChange(({ translationX }) => { + if (translationX > 0) { + translateX.value = translationX; + } + }) + .onEnd(() => { + if (translateX.value >= THRESHOLD) { + runOnJS(onSwipeToReply)(); + if (triggerHaptic) { + runOnJS(triggerHaptic)('impactMedium'); + } + } + translateX.value = withSpring( + 0, + { + dampingRatio: 1, + duration: 500, + overshootClamping: true, + stiffness: 1, + }, + () => { + isSwiping.value = false; + runOnJS(setIsBeingSwiped)(false); + }, + ); + }), + [isSwiping, messageSwipeToReplyHitSlop, onSwipeToReply, touchStart, translateX, triggerHaptic], + ); + + const messageBubbleAnimatedStyle = useAnimatedStyle( + () => + isSwiping.value + ? { + transform: [{ translateX: translateX.value }], + } + : {}, + [], + ); + + const swipeContentAnimatedStyle = useAnimatedStyle( + () => + isSwiping.value + ? { + opacity: interpolate(translateX.value, [0, THRESHOLD], [0, 1]), + transform: [ + { + translateX: interpolate( + translateX.value, + [0, THRESHOLD], + [-THRESHOLD, 0], + Extrapolation.CLAMP, + ), + }, + ], + } + : {}, + [], + ); const renderMessageBubble = useMemo( () => ( @@ -309,18 +338,31 @@ const MessageSimpleWithContext = < () => ( - - {MessageSwipeContent ? : null} - - {renderMessageBubble} + {isBeingSwiped ? ( + <> + + {MessageSwipeContent ? : null} + + + {renderMessageBubble} + + + ) : ( + renderMessageBubble + )} ), [ MessageSwipeContent, contentWrapper, + isBeingSwiped, messageBubbleAnimatedStyle, messageSwipeToReplyHitSlop, renderMessageBubble, From 3dcc08552d3e8443840389735d986022579f99ca Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Tue, 8 Apr 2025 17:45:53 +0200 Subject: [PATCH 06/13] fix: update snapshot --- .../__snapshots__/Thread.test.js.snap | 1106 +++++------------ 1 file changed, 291 insertions(+), 815 deletions(-) diff --git a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap index b05aa25bed..8b4be28659 100644 --- a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap +++ b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap @@ -405,254 +405,123 @@ exports[`Thread should match thread snapshot 1`] = ` style={ [ { - "position": "absolute", - }, - { - "opacity": undefined, - "transform": [ - { - "translateX": undefined, - }, - ], + "alignItems": "center", + "flexDirection": "row", }, {}, ] } > - - - - - - - - - - - - - - Message6 - + Message6 - + @@ -916,254 +785,123 @@ exports[`Thread should match thread snapshot 1`] = ` style={ [ { - "position": "absolute", - }, - { - "opacity": undefined, - "transform": [ - { - "translateX": undefined, - }, - ], + "alignItems": "center", + "flexDirection": "row", }, {}, ] } > - - - - - - - - - - - - - - Message5 - + Message5 - + @@ -1450,269 +1188,138 @@ exports[`Thread should match thread snapshot 1`] = ` "left": 750, "right": 750, } - } - style={ - [ - { - "alignItems": "center", - "flexDirection": "row", - }, - {}, - ] - } - > - - - - - - - - - - - + } + style={ + [ + { + "alignItems": "center", + "flexDirection": "row", + }, + {}, + ] + } + > - - - Message4 - + Message4 - + @@ -1969,254 +1576,123 @@ exports[`Thread should match thread snapshot 1`] = ` style={ [ { - "position": "absolute", - }, - { - "opacity": undefined, - "transform": [ - { - "translateX": undefined, - }, - ], + "alignItems": "center", + "flexDirection": "row", }, {}, ] } > - - - - - - - - - - - - - - Message3 - + Message3 - + From b03e78c23d25af73e9f70a71583dff3a4bfc8943 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Tue, 8 Apr 2025 22:48:45 +0200 Subject: [PATCH 07/13] perf: remove ref clearing --- package/src/components/MessageInput/MessageInput.tsx | 9 ++++----- .../contexts/messageInputContext/MessageInputContext.tsx | 4 ---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/package/src/components/MessageInput/MessageInput.tsx b/package/src/components/MessageInput/MessageInput.tsx index 74d3ab94a2..34c1cd1004 100644 --- a/package/src/components/MessageInput/MessageInput.tsx +++ b/package/src/components/MessageInput/MessageInput.tsx @@ -860,9 +860,8 @@ const MessageInputWithContext = < {shouldDisplayStopAIGeneration ? ( - ) : ( - isSendingButtonVisible() && - (cooldownRemainingSeconds ? ( + ) : isSendingButtonVisible() ? ( + cooldownRemainingSeconds ? ( ) : ( @@ -870,8 +869,8 @@ const MessageInputWithContext = < disabled={sending.current || !isValidMessage() || (giphyActive && !isOnline)} /> - )) - )} + ) + ) : null} {audioRecordingEnabled && isAudioRecorderAvailable() && !micLocked && ( []; for (const image of imageUploads) { if (enableOfflineSupport) { From 730b2c395416e6b735f1d996dce78b967e4192e9 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Tue, 8 Apr 2025 22:53:22 +0200 Subject: [PATCH 08/13] chore: remove console.logs --- package/src/components/Channel/Channel.tsx | 1 - package/src/components/MessageList/MessageList.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index aca079d06c..bb344e43e2 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -818,7 +818,6 @@ const ChannelWithContext = < // only update channel state if the events are not the previously subscribed useEffect's subscription events if (channel && channel.initialized) { - console.log('COPYING CHANNEL STATE LOL'); copyChannelState(); } } diff --git a/package/src/components/MessageList/MessageList.tsx b/package/src/components/MessageList/MessageList.tsx index 5434a65770..dc06381745 100644 --- a/package/src/components/MessageList/MessageList.tsx +++ b/package/src/components/MessageList/MessageList.tsx @@ -617,7 +617,6 @@ const MessageListWithContext = < setTimeout(() => { channelResyncScrollSet.current = true; if (channel.countUnread() > 0) { - console.log('marking read'); markRead(); } }, 500); From e9f7c217afd5cbfdcb0c0e91b671c305ab9c6486 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Wed, 9 Apr 2025 10:09:02 +0200 Subject: [PATCH 09/13] fix: ignore own typing events --- package/src/components/Channel/Channel.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index bb344e43e2..fef8f2eb74 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -781,9 +781,10 @@ const ChannelWithContext = < } // If the event is typing.start or typing.stop, set the typing state - const isTypingEvent = event.type === 'typing.start' || event.type === 'typing.stop'; - if (isTypingEvent) { - setTyping(channel); + if (event.type === 'typing.start' || event.type === 'typing.stop') { + if (event.user?.id !== client.userID) { + setTyping(channel); + } return; } else { if (thread?.id) { From c755738b32753b8879e2572c674754f0a70f8ba2 Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Wed, 9 Apr 2025 15:01:15 +0530 Subject: [PATCH 10/13] perf: memoize the suggestions context value --- package/src/components/Channel/Channel.tsx | 12 +++-- .../suggestionsContext/SuggestionsContext.tsx | 47 +++++++++++++------ 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index fef8f2eb74..ae5ca45407 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -1951,11 +1951,13 @@ const ChannelWithContext = < VideoThumbnail, }); - const suggestionsContext = { - AutoCompleteSuggestionHeader, - AutoCompleteSuggestionItem, - AutoCompleteSuggestionList, - }; + const suggestionsContext = useMemo(() => { + return { + AutoCompleteSuggestionHeader, + AutoCompleteSuggestionItem, + AutoCompleteSuggestionList, + }; + }, [AutoCompleteSuggestionHeader, AutoCompleteSuggestionItem, AutoCompleteSuggestionList]); const threadContext = useCreateThreadContext({ allowThreadMessagesInChannel, diff --git a/package/src/contexts/suggestionsContext/SuggestionsContext.tsx b/package/src/contexts/suggestionsContext/SuggestionsContext.tsx index 16fbb2c137..42794e6607 100644 --- a/package/src/contexts/suggestionsContext/SuggestionsContext.tsx +++ b/package/src/contexts/suggestionsContext/SuggestionsContext.tsx @@ -1,4 +1,4 @@ -import React, { PropsWithChildren, useContext, useState } from 'react'; +import React, { PropsWithChildren, useCallback, useContext, useMemo, useState } from 'react'; import type { CommandResponse, UserResponse } from 'stream-chat'; @@ -95,35 +95,54 @@ export const SuggestionsProvider = < children, value, }: PropsWithChildren<{ value?: Partial> }>) => { + const { AutoCompleteSuggestionHeader, AutoCompleteSuggestionItem, AutoCompleteSuggestionList } = + value ?? {}; const [triggerType, setTriggerType] = useState(null); const [suggestions, setSuggestions] = useState>(); const [suggestionsViewActive, setSuggestionsViewActive] = useState(false); - const openSuggestions = (component: SuggestionComponentType) => { + const openSuggestions = useCallback((component: SuggestionComponentType) => { setTriggerType(component); setSuggestionsViewActive(true); - }; - - const updateSuggestions = (newSuggestions: Suggestions) => { - setSuggestions(newSuggestions); - setSuggestionsViewActive(!!triggerType); - }; + }, []); + + const updateSuggestions = useCallback( + (newSuggestions: Suggestions) => { + setSuggestions(newSuggestions); + setSuggestionsViewActive(!!triggerType); + }, + [triggerType], + ); - const closeSuggestions = () => { + const closeSuggestions = useCallback(() => { setTriggerType(null); setSuggestions(undefined); setSuggestionsViewActive(false); - }; - - const suggestionsContext = { - ...value, + }, []); + + const suggestionsContext = useMemo(() => { + return { + AutoCompleteSuggestionHeader, + AutoCompleteSuggestionItem, + AutoCompleteSuggestionList, + closeSuggestions, + openSuggestions, + suggestions, + suggestionsViewActive, + triggerType, + updateSuggestions, + }; + }, [ + AutoCompleteSuggestionHeader, + AutoCompleteSuggestionItem, + AutoCompleteSuggestionList, closeSuggestions, openSuggestions, suggestions, suggestionsViewActive, triggerType, updateSuggestions, - }; + ]); return ( From d248440a6b18a6ab588f58e387a2923b75ef443e Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Wed, 9 Apr 2025 18:27:37 +0530 Subject: [PATCH 11/13] fix: autocomplete input re-render on received message --- .../AutoCompleteInput/AutoCompleteInput.tsx | 38 +++++++++---------- .../MessageInputContext.tsx | 2 + .../hooks/useCreateMessageInputContext.ts | 3 ++ 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/package/src/components/AutoCompleteInput/AutoCompleteInput.tsx b/package/src/components/AutoCompleteInput/AutoCompleteInput.tsx index df602a0034..f0652f0d75 100644 --- a/package/src/components/AutoCompleteInput/AutoCompleteInput.tsx +++ b/package/src/components/AutoCompleteInput/AutoCompleteInput.tsx @@ -3,10 +3,6 @@ import { I18nManager, StyleSheet, TextInput, TextInputProps } from 'react-native import throttle from 'lodash/throttle'; -import { - ChannelContextValue, - useChannelContext, -} from '../../contexts/channelContext/ChannelContext'; import { MessageInputContextValue, useMessageInputContext, @@ -53,22 +49,22 @@ const isCommand = (text: string) => text[0] === '/' && text.split(' ').length <= type AutoCompleteInputPropsWithContext< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'giphyEnabled'> & - Pick< - MessageInputContextValue, - | 'additionalTextInputProps' - | 'autoCompleteSuggestionsLimit' - | 'giphyActive' - | 'maxMessageLength' - | 'mentionAllAppUsersEnabled' - | 'mentionAllAppUsersQuery' - | 'numberOfLines' - | 'onChange' - | 'setGiphyActive' - | 'setInputBoxRef' - | 'text' - | 'triggerSettings' - > & +> = Pick< + MessageInputContextValue, + | 'additionalTextInputProps' + | 'autoCompleteSuggestionsLimit' + | 'giphyActive' + | 'giphyEnabled' + | 'maxMessageLength' + | 'mentionAllAppUsersEnabled' + | 'mentionAllAppUsersQuery' + | 'numberOfLines' + | 'onChange' + | 'setGiphyActive' + | 'setInputBoxRef' + | 'text' + | 'triggerSettings' +> & Pick< SuggestionsContextValue, 'closeSuggestions' | 'openSuggestions' | 'updateSuggestions' @@ -484,8 +480,8 @@ export const AutoCompleteInput = < >( props: AutoCompleteInputProps, ) => { - const { giphyEnabled } = useChannelContext(); const { + giphyEnabled, additionalTextInputProps, autoCompleteSuggestionsLimit, giphyActive, diff --git a/package/src/contexts/messageInputContext/MessageInputContext.tsx b/package/src/contexts/messageInputContext/MessageInputContext.tsx index 4b3e6d50f5..6d68ce722d 100644 --- a/package/src/contexts/messageInputContext/MessageInputContext.tsx +++ b/package/src/contexts/messageInputContext/MessageInputContext.tsx @@ -125,6 +125,7 @@ export type LocalMessageInputContext< url: string; }; }; + giphyEnabled: boolean; closeAttachmentPicker: () => void; /** The time at which the active cooldown will end */ cooldownEndsAt: Date; @@ -1446,6 +1447,7 @@ export const MessageInputProvider = < cooldownEndsAt, fileUploads, giphyActive, + giphyEnabled, imageUploads, inputBoxRef, isValidMessage, diff --git a/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts b/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts index ea42939ca3..1da854c817 100644 --- a/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts +++ b/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts @@ -41,6 +41,7 @@ export const useCreateMessageInputContext = < FileUploadPreview, fileUploads, giphyActive, + giphyEnabled, handleAttachButtonPress, hasCameraPicker, hasCommands, @@ -164,6 +165,7 @@ export const useCreateMessageInputContext = < FileUploadPreview, fileUploads, giphyActive, + giphyEnabled, handleAttachButtonPress, hasCameraPicker, hasCommands, @@ -246,6 +248,7 @@ export const useCreateMessageInputContext = < editingdep, fileUploadsValue, giphyActive, + giphyEnabled, imageUploadsValue, maxMessageLength, mentionedUsersLength, From eb88e4640181cc8429af8e436623f6ad18cbf160 Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Wed, 9 Apr 2025 18:58:45 +0530 Subject: [PATCH 12/13] perf: input buttons on text render issue --- .../src/components/MessageInput/InputButtons.tsx | 16 ++++++++-------- .../messageInputContext/MessageInputContext.tsx | 2 ++ .../hooks/useCreateMessageInputContext.ts | 3 +++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/package/src/components/MessageInput/InputButtons.tsx b/package/src/components/MessageInput/InputButtons.tsx index 4ffa702bee..ba50255312 100644 --- a/package/src/components/MessageInput/InputButtons.tsx +++ b/package/src/components/MessageInput/InputButtons.tsx @@ -29,12 +29,12 @@ export type InputButtonsWithContextProps< | 'hasCommands' | 'hasFilePicker' | 'hasImagePicker' + | 'hasText' | 'MoreOptionsButton' | 'openCommandsPicker' | 'selectedPicker' | 'setShowMoreOptions' | 'showMoreOptions' - | 'text' | 'toggleAttachmentPicker' >; @@ -51,11 +51,11 @@ export const InputButtonsWithContext = < hasCommands, hasFilePicker, hasImagePicker, + hasText, MoreOptionsButton, openCommandsPicker, setShowMoreOptions, showMoreOptions, - text, } = props; const { @@ -81,7 +81,7 @@ export const InputButtonsWithContext = < )} - {hasCommands && !text && ( + {hasCommands && !hasText && ( @@ -100,9 +100,9 @@ const areEqual = (); @@ -188,12 +188,12 @@ export const InputButtons = < hasCommands, hasFilePicker, hasImagePicker, + hasText, MoreOptionsButton, openCommandsPicker, selectedPicker, setShowMoreOptions, showMoreOptions, - text, toggleAttachmentPicker, }} {...props} diff --git a/package/src/contexts/messageInputContext/MessageInputContext.tsx b/package/src/contexts/messageInputContext/MessageInputContext.tsx index 6d68ce722d..d66efa11ef 100644 --- a/package/src/contexts/messageInputContext/MessageInputContext.tsx +++ b/package/src/contexts/messageInputContext/MessageInputContext.tsx @@ -152,6 +152,7 @@ export type LocalMessageInputContext< */ fileUploads: FileUpload[]; giphyActive: boolean; + hasText: boolean; /** * An array of image objects which are set for upload. It has the following structure: * @@ -1492,6 +1493,7 @@ export const MessageInputProvider = < uploadNewImage, ...value, closePollCreationDialog, + hasText: !!text, openPollCreationDialog, sendMessage, // overriding the originally passed in sendMessage showPollCreationDialog, diff --git a/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts b/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts index 1da854c817..b9e48da58f 100644 --- a/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts +++ b/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts @@ -47,6 +47,7 @@ export const useCreateMessageInputContext = < hasCommands, hasFilePicker, hasImagePicker, + hasText, ImageUploadPreview, imageUploads, initialValue, @@ -171,6 +172,7 @@ export const useCreateMessageInputContext = < hasCommands, hasFilePicker, hasImagePicker, + hasText, ImageUploadPreview, imageUploads, initialValue, @@ -249,6 +251,7 @@ export const useCreateMessageInputContext = < fileUploadsValue, giphyActive, giphyEnabled, + hasText, imageUploadsValue, maxMessageLength, mentionedUsersLength, From c85e689bb0969368a4cb07fc31d3c410b60dfb40 Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Wed, 9 Apr 2025 19:27:32 +0530 Subject: [PATCH 13/13] fix: useeffect re-render when text change in useMessageDetailsForState --- package/src/components/MessageInput/InputButtons.tsx | 8 ++++++-- .../src/components/MessageInput/MoreOptionsButton.tsx | 2 +- .../hooks/useMessageDetailsForState.ts | 10 ++++++---- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/package/src/components/MessageInput/InputButtons.tsx b/package/src/components/MessageInput/InputButtons.tsx index ba50255312..b842a1f5a8 100644 --- a/package/src/components/MessageInput/InputButtons.tsx +++ b/package/src/components/MessageInput/InputButtons.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { StyleSheet, View } from 'react-native'; import { @@ -64,6 +64,10 @@ export const InputButtonsWithContext = < }, } = useTheme(); + const handleShowMoreOptions = useCallback(() => { + setShowMoreOptions(true); + }, [setShowMoreOptions]); + const ownCapabilities = useOwnCapabilitiesContext(); if (giphyActive) { @@ -71,7 +75,7 @@ export const InputButtonsWithContext = < } return !showMoreOptions && (hasCameraPicker || hasImagePicker || hasFilePicker) && hasCommands ? ( - setShowMoreOptions(true)} /> + ) : ( <> {(hasCameraPicker || hasImagePicker || hasFilePicker) && ownCapabilities.uploadFile && ( diff --git a/package/src/components/MessageInput/MoreOptionsButton.tsx b/package/src/components/MessageInput/MoreOptionsButton.tsx index 300453cd39..813992adeb 100644 --- a/package/src/components/MessageInput/MoreOptionsButton.tsx +++ b/package/src/components/MessageInput/MoreOptionsButton.tsx @@ -1,6 +1,6 @@ import React from 'react'; import type { GestureResponderEvent } from 'react-native'; -import { TouchableOpacity } from 'react-native-gesture-handler'; +import { TouchableOpacity } from 'react-native'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { CircleRight } from '../../icons/CircleRight'; diff --git a/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts b/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts index 8708b6e4e1..9ff7843a73 100644 --- a/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts +++ b/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts @@ -22,20 +22,22 @@ export const useMessageDetailsForState = < const [imageUploads, setImageUploads] = useState([]); const [mentionedUsers, setMentionedUsers] = useState([]); const [numberOfUploads, setNumberOfUploads] = useState(0); - const [showMoreOptions, setShowMoreOptions] = useState(true); const initialTextValue = initialValue || ''; const [text, setText] = useState(initialTextValue); + const isEqualToInitialText = text === initialTextValue; + + const [showMoreOptions, setShowMoreOptions] = useState(true); + useEffect(() => { - if (text !== initialTextValue) { + if (!isEqualToInitialText) { setShowMoreOptions(false); } if (fileUploads.length || imageUploads.length) { setShowMoreOptions(false); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [text, imageUploads.length, fileUploads.length]); + }, [isEqualToInitialText, imageUploads.length, fileUploads.length]); const messageValue = message ? stringifyMessage(message) : '';