Skip to content

Commit 6c32f8b

Browse files
authored
fix: swipe to reply remounting (#3088)
* fix: avoid remounting stateful messages on swipe to reply * chore: renaming
1 parent 84010f1 commit 6c32f8b

File tree

2 files changed

+45
-8
lines changed

2 files changed

+45
-8
lines changed

package/src/components/Attachment/FileAttachmentGroup.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
33

44
import type { Attachment } from 'stream-chat';
@@ -43,6 +43,12 @@ const FileAttachmentGroupWithContext = (props: FileAttachmentGroupPropsWithConte
4343
files.map((file) => ({ ...file, duration: file.duration || 0, paused: true, progress: 0 })),
4444
);
4545

46+
useEffect(() => {
47+
setFilesToDisplay(
48+
files.map((file) => ({ ...file, duration: file.duration || 0, paused: true, progress: 0 })),
49+
);
50+
}, [files]);
51+
4652
// Handler triggered when an audio is loaded in the message input. The initial state is defined for the audio here and the duration is set.
4753
const onLoad = (index: string, duration: number) => {
4854
setFilesToDisplay((prevFilesToDisplay) =>

package/src/components/Message/MessageSimple/MessageSimple.tsx

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,16 @@ export type MessageSimplePropsWithContext = Pick<
9090
| 'reactionListPosition'
9191
| 'ReactionListTop'
9292
| 'setQuotedMessageState'
93-
>;
93+
> & {
94+
/**
95+
* Will determine whether the swipeable wrapper is always rendered for each
96+
* message. If set to false, the animated wrapper will be rendered only when
97+
* a swiping gesture is active and not otherwise.
98+
* Since stateful components would lose their state if we remount them while
99+
* an animation is happening, this should always be set to true in those instances.
100+
*/
101+
shouldRenderSwipeableWrapper: boolean;
102+
};
94103

95104
const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => {
96105
const [messageContentWidth, setMessageContentWidth] = useState(0);
@@ -123,6 +132,7 @@ const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => {
123132
ReactionListTop,
124133
setQuotedMessageState,
125134
showMessageStatus,
135+
shouldRenderSwipeableWrapper,
126136
} = props;
127137

128138
const {
@@ -202,7 +212,9 @@ const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => {
202212
const translateX = useSharedValue(0);
203213
const touchStart = useSharedValue<{ x: number; y: number } | null>(null);
204214
const isSwiping = useSharedValue<boolean>(false);
205-
const [isBeingSwiped, setIsBeingSwiped] = useState<boolean>(false);
215+
const [shouldRenderAnimatedWrapper, setShouldRenderAnimatedWrapper] = useState<boolean>(
216+
shouldRenderSwipeableWrapper,
217+
);
206218

207219
const onSwipeToReply = useCallback(() => {
208220
clearQuotedMessageState();
@@ -233,7 +245,9 @@ const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => {
233245
if (isHorizontalPanning) {
234246
state.activate();
235247
isSwiping.value = true;
236-
runOnJS(setIsBeingSwiped)(isSwiping.value);
248+
if (!shouldRenderSwipeableWrapper) {
249+
runOnJS(setShouldRenderAnimatedWrapper)(isSwiping.value);
250+
}
237251
} else {
238252
state.fail();
239253
}
@@ -263,11 +277,21 @@ const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => {
263277
stiffness: 1,
264278
},
265279
() => {
266-
runOnJS(setIsBeingSwiped)(isSwiping.value);
280+
if (!shouldRenderSwipeableWrapper) {
281+
runOnJS(setShouldRenderAnimatedWrapper)(isSwiping.value);
282+
}
267283
},
268284
);
269285
}),
270-
[isSwiping, messageSwipeToReplyHitSlop, onSwipeToReply, touchStart, translateX, triggerHaptic],
286+
[
287+
isSwiping,
288+
messageSwipeToReplyHitSlop,
289+
onSwipeToReply,
290+
touchStart,
291+
translateX,
292+
triggerHaptic,
293+
shouldRenderSwipeableWrapper,
294+
],
271295
);
272296

273297
const messageBubbleAnimatedStyle = useAnimatedStyle(
@@ -326,7 +350,7 @@ const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => {
326350
() => (
327351
<GestureDetector gesture={swipeGesture}>
328352
<View hitSlop={messageSwipeToReplyHitSlop} style={[styles.contentWrapper, contentWrapper]}>
329-
{isBeingSwiped ? (
353+
{shouldRenderAnimatedWrapper ? (
330354
<>
331355
<AnimatedWrapper
332356
style={[
@@ -350,7 +374,7 @@ const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => {
350374
[
351375
MessageSwipeContent,
352376
contentWrapper,
353-
isBeingSwiped,
377+
shouldRenderAnimatedWrapper,
354378
messageBubbleAnimatedStyle,
355379
messageSwipeToReplyHitSlop,
356380
renderMessageBubble,
@@ -586,6 +610,7 @@ export const MessageSimple = (props: MessageSimpleProps) => {
586610
onlyEmojis,
587611
otherAttachments,
588612
showMessageStatus,
613+
isMessageAIGenerated,
589614
} = useMessageContext();
590615
const {
591616
clearQuotedMessageState,
@@ -607,6 +632,11 @@ export const MessageSimple = (props: MessageSimpleProps) => {
607632
ReactionListTop,
608633
setQuotedMessageState,
609634
} = useMessagesContext();
635+
const isAIGenerated = useMemo(
636+
() => isMessageAIGenerated(message),
637+
[message, isMessageAIGenerated],
638+
);
639+
const shouldRenderSwipeableWrapper = (message?.attachments || []).length > 0 || isAIGenerated;
610640

611641
return (
612642
<MemoizedMessageSimple
@@ -639,6 +669,7 @@ export const MessageSimple = (props: MessageSimpleProps) => {
639669
reactionListPosition,
640670
ReactionListTop,
641671
setQuotedMessageState,
672+
shouldRenderSwipeableWrapper,
642673
showMessageStatus,
643674
}}
644675
{...props}

0 commit comments

Comments
 (0)