Skip to content

Commit 1fc7d98

Browse files
committed
fix: send button and audiorecordingbutton re-renders
1 parent bcf5fb4 commit 1fc7d98

File tree

4 files changed

+29
-117
lines changed

4 files changed

+29
-117
lines changed

package/src/components/MessageInput/MessageInput.tsx

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useEffect, useState } from 'react';
1+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
22
import { Modal, SafeAreaView, StyleSheet, View } from 'react-native';
33

44
import {
@@ -266,13 +266,6 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => {
266266

267267
const { seconds: cooldownRemainingSeconds } = useCountdown(cooldownEndsAt);
268268

269-
/**
270-
* Mounting and un-mounting logic are un-related in following useEffect.
271-
* While mounting we want to pass maxNumberOfFiles (which is prop on Channel component)
272-
* to AttachmentPicker (on OverlayProvider)
273-
*
274-
* While un-mounting, we want to close the picker e.g., while navigating away.
275-
*/
276269
useEffect(() => {
277270
attachmentManager.maxNumberOfFilesPerMessage = maxNumberOfFiles;
278271
if (doFileUploadRequest) {
@@ -542,16 +535,13 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => {
542535
waveformData,
543536
} = useAudioController();
544537

545-
const isSendingButtonVisible = () => {
546-
if (!(audioRecordingEnabled && isAudioRecorderAvailable())) {
547-
return true;
548-
}
538+
const asyncAudioEnabled = audioRecordingEnabled && isAudioRecorderAvailable();
539+
const hasText = !!text;
540+
const showSendingButton = hasText || attachments.length;
549541

550-
if ((text && text.trim()) || attachments.length) {
551-
return true;
552-
}
553-
return !recording;
554-
};
542+
const isSendingButtonVisible = useMemo(() => {
543+
return asyncAudioEnabled && showSendingButton && !recording;
544+
}, [asyncAudioEnabled, recording, showSendingButton]);
555545

556546
const micPositionX = useSharedValue(0);
557547
const micPositionY = useSharedValue(0);
@@ -753,7 +743,7 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => {
753743

754744
{shouldDisplayStopAIGeneration ? (
755745
<StopMessageStreamingButton onPress={stopGenerating} />
756-
) : isSendingButtonVisible() ? (
746+
) : isSendingButtonVisible ? (
757747
cooldownRemainingSeconds ? (
758748
<CooldownTimer seconds={cooldownRemainingSeconds} />
759749
) : (

package/src/components/MessageInput/MoreOptionsButton.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import React from 'react';
2-
import { TouchableOpacity } from 'react-native';
3-
import type { GestureResponderEvent } from 'react-native';
2+
import { Pressable } from 'react-native';
43

54
import { useTheme } from '../../contexts/themeContext/ThemeContext';
65
import { CircleRight } from '../../icons/CircleRight';
76

87
export type MoreOptionsButtonProps = {
98
/** Function that opens attachment options bottom sheet */
10-
handleOnPress?: ((event: GestureResponderEvent) => void) & (() => void);
9+
handleOnPress?: () => void;
1110
};
1211

1312
export const MoreOptionsButton = (props: MoreOptionsButtonProps) => {
@@ -21,14 +20,14 @@ export const MoreOptionsButton = (props: MoreOptionsButtonProps) => {
2120
} = useTheme();
2221

2322
return (
24-
<TouchableOpacity
23+
<Pressable
2524
hitSlop={{ bottom: 15, left: 15, right: 15, top: 15 }}
2625
onPress={handleOnPress}
27-
style={[moreOptionsButton]}
26+
style={({ pressed }) => [moreOptionsButton, { opacity: pressed ? 0.8 : 1 }]}
2827
testID='more-options-button'
2928
>
3029
<CircleRight pathFill={accent_blue} />
31-
</TouchableOpacity>
30+
</Pressable>
3231
);
3332
};
3433

package/src/components/MessageInput/SendButton.tsx

Lines changed: 7 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,19 @@ import { Search } from '../../icons/Search';
1515
import { SendRight } from '../../icons/SendRight';
1616
import { SendUp } from '../../icons/SendUp';
1717

18-
type SendButtonPropsWithContext = Pick<MessageInputContextValue, 'sendMessage'> & {
19-
/** Disables the button */ disabled: boolean;
18+
export type SendButtonProps = Partial<Pick<MessageInputContextValue, 'sendMessage'>> & {
19+
/** Disables the button */
20+
disabled: boolean;
2021
};
2122

2223
const customComposerDataSelector = (state: CustomDataManagerState) => ({
2324
command: state.custom.command,
2425
});
2526

26-
const SendButtonWithContext = (props: SendButtonPropsWithContext) => {
27-
const { disabled = false, sendMessage } = props;
27+
export const SendButton = (props: SendButtonProps) => {
28+
const { disabled = false, sendMessage: propsSendMessage } = props;
29+
const { sendMessage: sendMessageFromContext } = useMessageInputContext();
30+
const sendMessage = propsSendMessage || sendMessageFromContext;
2831
const messageComposer = useMessageComposer();
2932
const { customDataManager } = messageComposer;
3033
const { command } = useStateStore(customDataManager.state, customComposerDataSelector);
@@ -53,43 +56,4 @@ const SendButtonWithContext = (props: SendButtonPropsWithContext) => {
5356
);
5457
};
5558

56-
const areEqual = (prevProps: SendButtonPropsWithContext, nextProps: SendButtonPropsWithContext) => {
57-
const { disabled: prevDisabled, sendMessage: prevSendMessage } = prevProps;
58-
const { disabled: nextDisabled, sendMessage: nextSendMessage } = nextProps;
59-
60-
const disabledEqual = prevDisabled === nextDisabled;
61-
if (!disabledEqual) {
62-
return false;
63-
}
64-
65-
const sendMessageEqual = prevSendMessage === nextSendMessage;
66-
if (!sendMessageEqual) {
67-
return false;
68-
}
69-
70-
return true;
71-
};
72-
73-
const MemoizedSendButton = React.memo(
74-
SendButtonWithContext,
75-
areEqual,
76-
) as typeof SendButtonWithContext;
77-
78-
export type SendButtonProps = Partial<SendButtonPropsWithContext>;
79-
80-
/**
81-
* UI Component for send button in MessageInput component.
82-
*/
83-
export const SendButton = (props: SendButtonProps) => {
84-
const { sendMessage } = useMessageInputContext();
85-
86-
return (
87-
<MemoizedSendButton
88-
{...{ sendMessage }}
89-
{...props}
90-
{...{ disabled: props.disabled || false }}
91-
/>
92-
);
93-
};
94-
9559
SendButton.displayName = 'SendButton{messageInput}';

package/src/components/MessageInput/components/AudioRecorder/AudioRecordingButton.tsx

Lines changed: 9 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ import { useTranslationContext } from '../../../../contexts/translationContext/T
1010
import { Mic } from '../../../../icons/Mic';
1111
import { AudioRecordingReturnType, NativeHandlers } from '../../../../native';
1212

13-
type AudioRecordingButtonPropsWithContext = Pick<
14-
MessageInputContextValue,
15-
'asyncMessagesMinimumPressDuration'
13+
export type AudioRecordingButtonProps = Partial<
14+
Pick<MessageInputContextValue, 'asyncMessagesMinimumPressDuration'>
1615
> & {
1716
/**
1817
* The current voice recording that is in progress.
@@ -40,16 +39,21 @@ type AudioRecordingButtonPropsWithContext = Pick<
4039
startVoiceRecording?: () => Promise<void>;
4140
};
4241

43-
const AudioRecordingButtonWithContext = (props: AudioRecordingButtonPropsWithContext) => {
42+
export const AudioRecordingButton = (props: AudioRecordingButtonProps) => {
4443
const {
45-
asyncMessagesMinimumPressDuration,
44+
asyncMessagesMinimumPressDuration: propsAsyncMessagesMinimumPressDuration,
4645
buttonSize,
4746
handleLongPress,
4847
handlePress,
4948
permissionsGranted,
5049
recording,
5150
startVoiceRecording,
5251
} = props;
52+
const { asyncMessagesMinimumPressDuration: contextAsyncMessagesMinimumPressDuration } =
53+
useMessageInputContext();
54+
55+
const asyncMessagesMinimumPressDuration =
56+
propsAsyncMessagesMinimumPressDuration || contextAsyncMessagesMinimumPressDuration;
5357

5458
const {
5559
theme: {
@@ -116,51 +120,6 @@ const AudioRecordingButtonWithContext = (props: AudioRecordingButtonPropsWithCon
116120
);
117121
};
118122

119-
const areEqual = (
120-
prevProps: AudioRecordingButtonPropsWithContext,
121-
nextProps: AudioRecordingButtonPropsWithContext,
122-
) => {
123-
const {
124-
asyncMessagesMinimumPressDuration: prevAsyncMessagesMinimumPressDuration,
125-
recording: prevRecording,
126-
} = prevProps;
127-
const {
128-
asyncMessagesMinimumPressDuration: nextAsyncMessagesMinimumPressDuration,
129-
recording: nextRecording,
130-
} = nextProps;
131-
132-
const asyncMessagesMinimumPressDurationEqual =
133-
prevAsyncMessagesMinimumPressDuration === nextAsyncMessagesMinimumPressDuration;
134-
if (!asyncMessagesMinimumPressDurationEqual) {
135-
return false;
136-
}
137-
138-
const recordingEqual = prevRecording === nextRecording;
139-
if (!recordingEqual) {
140-
return false;
141-
}
142-
143-
return true;
144-
};
145-
146-
const MemoizedAudioRecordingButton = React.memo(
147-
AudioRecordingButtonWithContext,
148-
areEqual,
149-
) as typeof AudioRecordingButtonWithContext;
150-
151-
export type AudioRecordingButtonProps = Partial<AudioRecordingButtonPropsWithContext> & {
152-
recording: AudioRecordingReturnType;
153-
};
154-
155-
/**
156-
* Component to display the mic button on the Message Input.
157-
*/
158-
export const AudioRecordingButton = (props: AudioRecordingButtonProps) => {
159-
const { asyncMessagesMinimumPressDuration } = useMessageInputContext();
160-
161-
return <MemoizedAudioRecordingButton {...{ asyncMessagesMinimumPressDuration }} {...props} />;
162-
};
163-
164123
const styles = StyleSheet.create({
165124
container: {
166125
alignItems: 'center',

0 commit comments

Comments
 (0)