Skip to content

Commit 83d594f

Browse files
committed
fix: swipe to reply remounting (#3088)
* fix: avoid remounting stateful messages on swipe to reply * chore: renaming
1 parent 2bfad9d commit 83d594f

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';
@@ -51,6 +51,12 @@ const FileAttachmentGroupWithContext = <
5151
files.map((file) => ({ ...file, duration: file.duration || 0, paused: true, progress: 0 })),
5252
);
5353

54+
useEffect(() => {
55+
setFilesToDisplay(
56+
files.map((file) => ({ ...file, duration: file.duration || 0, paused: true, progress: 0 })),
57+
);
58+
}, [files]);
59+
5460
// 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.
5561
const onLoad = (index: string, duration: number) => {
5662
setFilesToDisplay((prevFilesToDisplay) =>

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

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

97106
const MessageSimpleWithContext = <
98107
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
@@ -129,6 +138,7 @@ const MessageSimpleWithContext = <
129138
ReactionListTop,
130139
setQuotedMessageState,
131140
showMessageStatus,
141+
shouldRenderSwipeableWrapper,
132142
} = props;
133143

134144
const {
@@ -208,7 +218,9 @@ const MessageSimpleWithContext = <
208218
const translateX = useSharedValue(0);
209219
const touchStart = useSharedValue<{ x: number; y: number } | null>(null);
210220
const isSwiping = useSharedValue<boolean>(false);
211-
const [isBeingSwiped, setIsBeingSwiped] = useState<boolean>(false);
221+
const [shouldRenderAnimatedWrapper, setShouldRenderAnimatedWrapper] = useState<boolean>(
222+
shouldRenderSwipeableWrapper,
223+
);
212224

213225
const onSwipeToReply = useCallback(() => {
214226
clearQuotedMessageState();
@@ -239,7 +251,9 @@ const MessageSimpleWithContext = <
239251
if (isHorizontalPanning) {
240252
state.activate();
241253
isSwiping.value = true;
242-
runOnJS(setIsBeingSwiped)(isSwiping.value);
254+
if (!shouldRenderSwipeableWrapper) {
255+
runOnJS(setShouldRenderAnimatedWrapper)(isSwiping.value);
256+
}
243257
} else {
244258
state.fail();
245259
}
@@ -269,11 +283,21 @@ const MessageSimpleWithContext = <
269283
stiffness: 1,
270284
},
271285
() => {
272-
runOnJS(setIsBeingSwiped)(isSwiping.value);
286+
if (!shouldRenderSwipeableWrapper) {
287+
runOnJS(setShouldRenderAnimatedWrapper)(isSwiping.value);
288+
}
273289
},
274290
);
275291
}),
276-
[isSwiping, messageSwipeToReplyHitSlop, onSwipeToReply, touchStart, translateX, triggerHaptic],
292+
[
293+
isSwiping,
294+
messageSwipeToReplyHitSlop,
295+
onSwipeToReply,
296+
touchStart,
297+
translateX,
298+
triggerHaptic,
299+
shouldRenderSwipeableWrapper,
300+
],
277301
);
278302

279303
const messageBubbleAnimatedStyle = useAnimatedStyle(
@@ -332,7 +356,7 @@ const MessageSimpleWithContext = <
332356
() => (
333357
<GestureDetector gesture={swipeGesture}>
334358
<View hitSlop={messageSwipeToReplyHitSlop} style={[styles.contentWrapper, contentWrapper]}>
335-
{isBeingSwiped ? (
359+
{shouldRenderAnimatedWrapper ? (
336360
<>
337361
<AnimatedWrapper
338362
style={[
@@ -356,7 +380,7 @@ const MessageSimpleWithContext = <
356380
[
357381
MessageSwipeContent,
358382
contentWrapper,
359-
isBeingSwiped,
383+
shouldRenderAnimatedWrapper,
360384
messageBubbleAnimatedStyle,
361385
messageSwipeToReplyHitSlop,
362386
renderMessageBubble,
@@ -598,6 +622,7 @@ export const MessageSimple = <
598622
onlyEmojis,
599623
otherAttachments,
600624
showMessageStatus,
625+
isMessageAIGenerated,
601626
} = useMessageContext<StreamChatGenerics>();
602627
const {
603628
clearQuotedMessageState,
@@ -619,6 +644,11 @@ export const MessageSimple = <
619644
ReactionListTop,
620645
setQuotedMessageState,
621646
} = useMessagesContext<StreamChatGenerics>();
647+
const isAIGenerated = useMemo(
648+
() => isMessageAIGenerated(message),
649+
[message, isMessageAIGenerated],
650+
);
651+
const shouldRenderSwipeableWrapper = (message?.attachments || []).length > 0 || isAIGenerated;
622652

623653
return (
624654
<MemoizedMessageSimple<StreamChatGenerics>
@@ -651,6 +681,7 @@ export const MessageSimple = <
651681
reactionListPosition,
652682
ReactionListTop,
653683
setQuotedMessageState,
684+
shouldRenderSwipeableWrapper,
654685
showMessageStatus,
655686
}}
656687
{...props}

0 commit comments

Comments
 (0)