Skip to content

Commit 0c37e4c

Browse files
committed
fix: memoize message bubble components to be in line with previous behaviour
1 parent ddc38aa commit 0c37e4c

File tree

1 file changed

+182
-178
lines changed

1 file changed

+182
-178
lines changed

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

Lines changed: 182 additions & 178 deletions
Original file line numberDiff line numberDiff line change
@@ -33,192 +33,196 @@ export type MessageBubbleProps = Pick<
3333
> &
3434
Pick<ReactionListTopProps, 'messageContentWidth'>;
3535

36-
export const MessageBubble = ({
37-
reactionListPosition,
38-
messageContentWidth,
39-
setMessageContentWidth,
40-
MessageContent,
41-
ReactionListTop,
42-
backgroundColor,
43-
isVeryLastMessage,
44-
messageGroupedSingleOrBottom,
45-
noBorder,
46-
}: MessageBubbleProps) => {
47-
const {
48-
theme: {
49-
messageSimple: { contentWrapper },
50-
},
51-
} = useTheme();
52-
53-
return (
54-
<View style={[styles.contentWrapper, contentWrapper]}>
55-
<MessageContent
56-
backgroundColor={backgroundColor}
57-
isVeryLastMessage={isVeryLastMessage}
58-
messageGroupedSingleOrBottom={messageGroupedSingleOrBottom}
59-
noBorder={noBorder}
60-
setMessageContentWidth={setMessageContentWidth}
61-
/>
62-
{reactionListPosition === 'top' && ReactionListTop ? (
63-
<ReactionListTop messageContentWidth={messageContentWidth} />
64-
) : null}
65-
</View>
66-
);
67-
};
36+
export const MessageBubble = React.memo(
37+
({
38+
reactionListPosition,
39+
messageContentWidth,
40+
setMessageContentWidth,
41+
MessageContent,
42+
ReactionListTop,
43+
backgroundColor,
44+
isVeryLastMessage,
45+
messageGroupedSingleOrBottom,
46+
noBorder,
47+
}: MessageBubbleProps) => {
48+
const {
49+
theme: {
50+
messageSimple: { contentWrapper },
51+
},
52+
} = useTheme();
53+
54+
return (
55+
<View style={[styles.contentWrapper, contentWrapper]}>
56+
<MessageContent
57+
backgroundColor={backgroundColor}
58+
isVeryLastMessage={isVeryLastMessage}
59+
messageGroupedSingleOrBottom={messageGroupedSingleOrBottom}
60+
noBorder={noBorder}
61+
setMessageContentWidth={setMessageContentWidth}
62+
/>
63+
{reactionListPosition === 'top' && ReactionListTop ? (
64+
<ReactionListTop messageContentWidth={messageContentWidth} />
65+
) : null}
66+
</View>
67+
);
68+
},
69+
);
6870

6971
const AnimatedWrapper = Animated.createAnimatedComponent(View);
7072

71-
export const SwipableMessageBubble = (
72-
props: MessageBubbleProps &
73-
Pick<MessagesContextValue, 'MessageSwipeContent'> &
74-
Pick<
75-
MessageSimplePropsWithContext,
76-
'shouldRenderSwipeableWrapper' | 'messageSwipeToReplyHitSlop'
77-
> & { onSwipe: () => void },
78-
) => {
79-
const {
80-
MessageSwipeContent,
81-
shouldRenderSwipeableWrapper,
82-
messageSwipeToReplyHitSlop,
83-
onSwipe,
84-
...messageBubbleProps
85-
} = props;
86-
87-
const {
88-
theme: {
89-
messageSimple: { contentWrapper, swipeContentContainer },
90-
},
91-
} = useTheme();
92-
93-
const translateX = useSharedValue(0);
94-
const touchStart = useSharedValue<{ x: number; y: number } | null>(null);
95-
const isSwiping = useSharedValue<boolean>(false);
96-
const [shouldRenderAnimatedWrapper, setShouldRenderAnimatedWrapper] = useState<boolean>(
97-
shouldRenderSwipeableWrapper,
98-
);
99-
100-
const SWIPABLE_THRESHOLD = 25;
101-
102-
const triggerHaptic = NativeHandlers.triggerHaptic;
103-
104-
const swipeGesture = useMemo(
105-
() =>
106-
Gesture.Pan()
107-
.hitSlop(messageSwipeToReplyHitSlop)
108-
.onBegin((event) => {
109-
touchStart.value = { x: event.x, y: event.y };
110-
})
111-
.onTouchesMove((event, state) => {
112-
if (!touchStart.value || !event.changedTouches.length) {
113-
state.fail();
114-
return;
115-
}
116-
117-
const xDiff = Math.abs(event.changedTouches[0].x - touchStart.value.x);
118-
const yDiff = Math.abs(event.changedTouches[0].y - touchStart.value.y);
119-
const isHorizontalPanning = xDiff > yDiff;
120-
121-
if (isHorizontalPanning) {
122-
state.activate();
123-
isSwiping.value = true;
124-
if (!shouldRenderSwipeableWrapper) {
125-
runOnJS(setShouldRenderAnimatedWrapper)(isSwiping.value);
126-
}
127-
} else {
128-
state.fail();
129-
}
130-
})
131-
.onStart(() => {
132-
translateX.value = 0;
133-
})
134-
.onChange(({ translationX }) => {
135-
if (translationX > 0) {
136-
translateX.value = translationX;
137-
}
138-
})
139-
.onEnd(() => {
140-
if (translateX.value >= SWIPABLE_THRESHOLD) {
141-
runOnJS(onSwipe)();
142-
if (triggerHaptic) {
143-
runOnJS(triggerHaptic)('impactMedium');
73+
export const SwipableMessageBubble = React.memo(
74+
(
75+
props: MessageBubbleProps &
76+
Pick<MessagesContextValue, 'MessageSwipeContent'> &
77+
Pick<
78+
MessageSimplePropsWithContext,
79+
'shouldRenderSwipeableWrapper' | 'messageSwipeToReplyHitSlop'
80+
> & { onSwipe: () => void },
81+
) => {
82+
const {
83+
MessageSwipeContent,
84+
shouldRenderSwipeableWrapper,
85+
messageSwipeToReplyHitSlop,
86+
onSwipe,
87+
...messageBubbleProps
88+
} = props;
89+
90+
const {
91+
theme: {
92+
messageSimple: { contentWrapper, swipeContentContainer },
93+
},
94+
} = useTheme();
95+
96+
const translateX = useSharedValue(0);
97+
const touchStart = useSharedValue<{ x: number; y: number } | null>(null);
98+
const isSwiping = useSharedValue<boolean>(false);
99+
const [shouldRenderAnimatedWrapper, setShouldRenderAnimatedWrapper] = useState<boolean>(
100+
shouldRenderSwipeableWrapper,
101+
);
102+
103+
const SWIPABLE_THRESHOLD = 25;
104+
105+
const triggerHaptic = NativeHandlers.triggerHaptic;
106+
107+
const swipeGesture = useMemo(
108+
() =>
109+
Gesture.Pan()
110+
.hitSlop(messageSwipeToReplyHitSlop)
111+
.onBegin((event) => {
112+
touchStart.value = { x: event.x, y: event.y };
113+
})
114+
.onTouchesMove((event, state) => {
115+
if (!touchStart.value || !event.changedTouches.length) {
116+
state.fail();
117+
return;
144118
}
145-
}
146-
isSwiping.value = false;
147-
translateX.value = withSpring(
148-
0,
149-
{
150-
dampingRatio: 1,
151-
duration: 500,
152-
overshootClamping: true,
153-
stiffness: 1,
154-
},
155-
() => {
119+
120+
const xDiff = Math.abs(event.changedTouches[0].x - touchStart.value.x);
121+
const yDiff = Math.abs(event.changedTouches[0].y - touchStart.value.y);
122+
const isHorizontalPanning = xDiff > yDiff;
123+
124+
if (isHorizontalPanning) {
125+
state.activate();
126+
isSwiping.value = true;
156127
if (!shouldRenderSwipeableWrapper) {
157128
runOnJS(setShouldRenderAnimatedWrapper)(isSwiping.value);
158129
}
159-
},
160-
);
161-
}),
162-
[
163-
isSwiping,
164-
messageSwipeToReplyHitSlop,
165-
onSwipe,
166-
touchStart,
167-
translateX,
168-
triggerHaptic,
169-
shouldRenderSwipeableWrapper,
170-
],
171-
);
172-
173-
const messageBubbleAnimatedStyle = useAnimatedStyle(
174-
() => ({
175-
transform: [{ translateX: translateX.value }],
176-
}),
177-
[],
178-
);
179-
180-
const swipeContentAnimatedStyle = useAnimatedStyle(
181-
() => ({
182-
opacity: interpolate(translateX.value, [0, SWIPABLE_THRESHOLD], [0, 1]),
183-
transform: [
184-
{
185-
translateX: interpolate(
186-
translateX.value,
187-
[0, SWIPABLE_THRESHOLD],
188-
[-SWIPABLE_THRESHOLD, 0],
189-
Extrapolation.CLAMP,
190-
),
191-
},
130+
} else {
131+
state.fail();
132+
}
133+
})
134+
.onStart(() => {
135+
translateX.value = 0;
136+
})
137+
.onChange(({ translationX }) => {
138+
if (translationX > 0) {
139+
translateX.value = translationX;
140+
}
141+
})
142+
.onEnd(() => {
143+
if (translateX.value >= SWIPABLE_THRESHOLD) {
144+
runOnJS(onSwipe)();
145+
if (triggerHaptic) {
146+
runOnJS(triggerHaptic)('impactMedium');
147+
}
148+
}
149+
isSwiping.value = false;
150+
translateX.value = withSpring(
151+
0,
152+
{
153+
dampingRatio: 1,
154+
duration: 500,
155+
overshootClamping: true,
156+
stiffness: 1,
157+
},
158+
() => {
159+
if (!shouldRenderSwipeableWrapper) {
160+
runOnJS(setShouldRenderAnimatedWrapper)(isSwiping.value);
161+
}
162+
},
163+
);
164+
}),
165+
[
166+
isSwiping,
167+
messageSwipeToReplyHitSlop,
168+
onSwipe,
169+
touchStart,
170+
translateX,
171+
triggerHaptic,
172+
shouldRenderSwipeableWrapper,
192173
],
193-
}),
194-
[],
195-
);
196-
197-
return (
198-
<GestureDetector gesture={swipeGesture}>
199-
<View hitSlop={messageSwipeToReplyHitSlop} style={[styles.contentWrapper, contentWrapper]}>
200-
{shouldRenderAnimatedWrapper ? (
201-
<>
202-
<AnimatedWrapper
203-
style={[
204-
styles.swipeContentContainer,
205-
swipeContentAnimatedStyle,
206-
swipeContentContainer,
207-
]}
208-
>
209-
{MessageSwipeContent ? <MessageSwipeContent /> : null}
210-
</AnimatedWrapper>
211-
<AnimatedWrapper pointerEvents='box-none' style={messageBubbleAnimatedStyle}>
212-
<MessageBubble {...messageBubbleProps} />
213-
</AnimatedWrapper>
214-
</>
215-
) : (
216-
<MessageBubble {...messageBubbleProps} />
217-
)}
218-
</View>
219-
</GestureDetector>
220-
);
221-
};
174+
);
175+
176+
const messageBubbleAnimatedStyle = useAnimatedStyle(
177+
() => ({
178+
transform: [{ translateX: translateX.value }],
179+
}),
180+
[],
181+
);
182+
183+
const swipeContentAnimatedStyle = useAnimatedStyle(
184+
() => ({
185+
opacity: interpolate(translateX.value, [0, SWIPABLE_THRESHOLD], [0, 1]),
186+
transform: [
187+
{
188+
translateX: interpolate(
189+
translateX.value,
190+
[0, SWIPABLE_THRESHOLD],
191+
[-SWIPABLE_THRESHOLD, 0],
192+
Extrapolation.CLAMP,
193+
),
194+
},
195+
],
196+
}),
197+
[],
198+
);
199+
200+
return (
201+
<GestureDetector gesture={swipeGesture}>
202+
<View hitSlop={messageSwipeToReplyHitSlop} style={[styles.contentWrapper, contentWrapper]}>
203+
{shouldRenderAnimatedWrapper ? (
204+
<>
205+
<AnimatedWrapper
206+
style={[
207+
styles.swipeContentContainer,
208+
swipeContentAnimatedStyle,
209+
swipeContentContainer,
210+
]}
211+
>
212+
{MessageSwipeContent ? <MessageSwipeContent /> : null}
213+
</AnimatedWrapper>
214+
<AnimatedWrapper pointerEvents='box-none' style={messageBubbleAnimatedStyle}>
215+
<MessageBubble {...messageBubbleProps} />
216+
</AnimatedWrapper>
217+
</>
218+
) : (
219+
<MessageBubble {...messageBubbleProps} />
220+
)}
221+
</View>
222+
</GestureDetector>
223+
);
224+
},
225+
);
222226

223227
const styles = StyleSheet.create({
224228
contentWrapper: {

0 commit comments

Comments
 (0)