From 2b86fbcbd7ae7146857a48ae667ec5eec75fac23 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Mon, 6 Apr 2026 18:16:01 +0200 Subject: [PATCH] perf: improve animation performance --- .../MessageInput/MessageComposer.tsx | 8 ++++---- .../MessageComposerLeadingView.tsx | 5 +++-- .../MessageInput/MessageInputHeaderView.tsx | 9 +++++---- .../MessageInput/MessageInputTrailingView.tsx | 5 +++-- .../components/OutputButtons/index.tsx | 19 ++++++++++--------- .../MessageList/MessageFlashList.tsx | 5 +++-- .../components/MessageList/MessageList.tsx | 9 ++++----- .../src/state-store/message-overlay-store.ts | 8 +------- package/src/utils/transitions.ts | 9 +++++++++ 9 files changed, 42 insertions(+), 35 deletions(-) create mode 100644 package/src/utils/transitions.ts diff --git a/package/src/components/MessageInput/MessageComposer.tsx b/package/src/components/MessageInput/MessageComposer.tsx index c6e2d42015..d3cc6eecb6 100644 --- a/package/src/components/MessageInput/MessageComposer.tsx +++ b/package/src/components/MessageInput/MessageComposer.tsx @@ -5,7 +5,6 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler'; import Animated, { Extrapolation, interpolate, - LinearTransition, useAnimatedStyle, useSharedValue, } from 'react-native-reanimated'; @@ -54,6 +53,7 @@ import { useStateStore } from '../../hooks/useStateStore'; import { AudioRecorderManagerState } from '../../state-store/audio-recorder-manager'; import { MessageInputHeightState } from '../../state-store/message-input-height-store'; import { primitives } from '../../theme'; +import { transitions } from '../../utils/transitions'; import { type TextInputOverrideComponent } from '../AutoCompleteInput/AutoCompleteInput'; import { CreatePoll } from '../Poll/CreatePollContent'; import { PortalWhileClosingView } from '../UIComponents/PortalWhileClosingView'; @@ -383,7 +383,7 @@ const MessageComposerWithContext = (props: MessageComposerPropsWithContext) => { ] : null } - layout={LinearTransition.duration(200)} + layout={transitions.layout200} > { { {!isRecordingStateIdle ? ( diff --git a/package/src/components/MessageInput/MessageComposerLeadingView.tsx b/package/src/components/MessageInput/MessageComposerLeadingView.tsx index e4918d5d58..137bd428ed 100644 --- a/package/src/components/MessageInput/MessageComposerLeadingView.tsx +++ b/package/src/components/MessageInput/MessageComposerLeadingView.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { StyleSheet } from 'react-native'; -import Animated, { LinearTransition } from 'react-native-reanimated'; +import Animated from 'react-native-reanimated'; import { InputButtons } from './components/InputButtons'; import { idleRecordingStateSelector } from './utils/audioRecorderSelectors'; @@ -9,6 +9,7 @@ import { idleRecordingStateSelector } from './utils/audioRecorderSelectors'; import { useMessageInputContext } from '../../contexts/messageInputContext/MessageInputContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useStateStore } from '../../hooks/useStateStore'; +import { transitions } from '../../utils/transitions'; export const MessageComposerLeadingView = () => { const { @@ -24,7 +25,7 @@ export const MessageComposerLeadingView = () => { return isRecordingStateIdle ? ( { const { @@ -42,7 +43,7 @@ export const MessageInputHeaderView = () => { return isRecordingStateIdle ? ( { ]} > {editing ? ( - + { ) : null} {quotedMessage && !editing ? ( - + ) : null} diff --git a/package/src/components/MessageInput/MessageInputTrailingView.tsx b/package/src/components/MessageInput/MessageInputTrailingView.tsx index 7b1a38fe21..93d83e3035 100644 --- a/package/src/components/MessageInput/MessageInputTrailingView.tsx +++ b/package/src/components/MessageInput/MessageInputTrailingView.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { StyleSheet } from 'react-native'; -import Animated, { LinearTransition } from 'react-native-reanimated'; +import Animated from 'react-native-reanimated'; import { OutputButtons } from './components/OutputButtons'; @@ -11,6 +11,7 @@ import { useMessageInputContext } from '../../contexts/messageInputContext/Messa import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useStateStore } from '../../hooks/useStateStore'; import { primitives } from '../../theme'; +import { transitions } from '../../utils/transitions'; export const MessageInputTrailingView = () => { const { @@ -25,7 +26,7 @@ export const MessageInputTrailingView = () => { ); return (recordingStatus === 'idle' || recordingStatus === 'recording') && !micLocked ? ( diff --git a/package/src/components/MessageInput/components/OutputButtons/index.tsx b/package/src/components/MessageInput/components/OutputButtons/index.tsx index 37b2cf9845..283a9dc7a7 100644 --- a/package/src/components/MessageInput/components/OutputButtons/index.tsx +++ b/package/src/components/MessageInput/components/OutputButtons/index.tsx @@ -1,6 +1,6 @@ import React, { useCallback } from 'react'; -import Animated, { ZoomIn, ZoomOut } from 'react-native-reanimated'; +import Animated from 'react-native-reanimated'; import { TextComposerState } from 'stream-chat'; @@ -21,6 +21,7 @@ import { useMessageInputContext, } from '../../../../contexts/messageInputContext/MessageInputContext'; import { useStateStore } from '../../../../hooks/useStateStore'; +import { transitions } from '../../../../utils/transitions'; import { AIStates, useAIState } from '../../../AITypingIndicatorView'; import { useIsCooldownActive } from '../../hooks/useIsCooldownActive'; @@ -88,8 +89,8 @@ export const OutputButtonsWithContext = (props: OutputButtonsWithContextProps) = } else if (editing || command) { return ( @@ -99,8 +100,8 @@ export const OutputButtonsWithContext = (props: OutputButtonsWithContextProps) = } else if (cooldownIsActive) { return ( @@ -110,8 +111,8 @@ export const OutputButtonsWithContext = (props: OutputButtonsWithContextProps) = } else if (audioRecordingEnabled && textIsEmpty && !hasAttachments) { return ( @@ -121,8 +122,8 @@ export const OutputButtonsWithContext = (props: OutputButtonsWithContextProps) = } else { return ( diff --git a/package/src/components/MessageList/MessageFlashList.tsx b/package/src/components/MessageList/MessageFlashList.tsx index f89bfc7ab7..6faae7522e 100644 --- a/package/src/components/MessageList/MessageFlashList.tsx +++ b/package/src/components/MessageList/MessageFlashList.tsx @@ -8,7 +8,7 @@ import { ViewToken, } from 'react-native'; -import Animated, { LinearTransition } from 'react-native-reanimated'; +import Animated from 'react-native-reanimated'; import type { FlashListProps, FlashListRef } from '@shopify/flash-list'; import type { Channel, Event, LocalMessage, MessageResponse } from 'stream-chat'; @@ -56,6 +56,7 @@ import { useStableCallback, useStateStore } from '../../hooks'; import { bumpOverlayLayoutRevision } from '../../state-store'; import { MessageInputHeightState } from '../../state-store/message-input-height-store'; import { primitives } from '../../theme'; +import { transitions } from '../../utils/transitions'; import { MessageWrapper } from '../Message/MessageItemView/MessageWrapper'; type FlashListContextApi = { getRef?: () => FlashListRef | null } | undefined; @@ -1094,7 +1095,7 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => ) : null} { { {scrollToBottomButtonVisible ? ( { const AnimatedList = React.memo( Animated.createAnimatedComponent(FlatList), ); - -const LayoutTransition = LinearTransition.duration(200); diff --git a/package/src/state-store/message-overlay-store.ts b/package/src/state-store/message-overlay-store.ts index beafbb5ba7..6acbb9eb95 100644 --- a/package/src/state-store/message-overlay-store.ts +++ b/package/src/state-store/message-overlay-store.ts @@ -94,13 +94,7 @@ export const closeOverlay = () => { return; } - requestAnimationFrame(() => { - if (!overlayStore.getLatestValue().id) { - return; - } - - overlayStore.partialNext({ closing: true }); - }); + overlayStore.partialNext({ closing: true }); }; let actionQueue: Array<() => void | Promise> = []; diff --git a/package/src/utils/transitions.ts b/package/src/utils/transitions.ts new file mode 100644 index 0000000000..d5d8676583 --- /dev/null +++ b/package/src/utils/transitions.ts @@ -0,0 +1,9 @@ +import { FadeIn, FadeOut, LinearTransition, ZoomIn, ZoomOut } from 'react-native-reanimated'; + +export const transitions = { + fadeIn200: FadeIn.duration(200), + fadeOut200: FadeOut.duration(200), + layout200: LinearTransition.duration(200), + zoomIn200: ZoomIn.duration(200), + zoomOut200: ZoomOut.duration(200), +} as const;