From e014777c9e676b0bfb57ade6c0e8e0d161332762 Mon Sep 17 00:00:00 2001 From: Danish Ahmed Mirza Date: Thu, 14 Jul 2022 19:16:29 +0530 Subject: [PATCH] useMemo performance optamizations and other requested changes --- app/containers/EmojiPicker/EmojiCategory.tsx | 104 +++++++++++-------- app/containers/EmojiPicker/Footer.tsx | 8 +- app/containers/EmojiPicker/TabBar.tsx | 22 ++-- app/containers/EmojiPicker/index.tsx | 68 ++++++------ app/containers/EmojiPicker/styles.ts | 6 +- app/containers/MessageBox/EmojiSearchbar.tsx | 13 +-- 6 files changed, 124 insertions(+), 97 deletions(-) diff --git a/app/containers/EmojiPicker/EmojiCategory.tsx b/app/containers/EmojiPicker/EmojiCategory.tsx index 0b00239a3..33d8078f9 100644 --- a/app/containers/EmojiPicker/EmojiCategory.tsx +++ b/app/containers/EmojiPicker/EmojiCategory.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Text, TouchableOpacity } from 'react-native'; +import { Text, Pressable } from 'react-native'; import { BottomSheetFlatList } from '@gorhom/bottom-sheet'; import { FlatList as GHFlatList } from 'react-native-gesture-handler'; @@ -8,10 +8,18 @@ import styles from './styles'; import CustomEmoji from './CustomEmoji'; import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps'; import { IEmoji, IEmojiCategory } from '../../definitions/IEmoji'; +import { useTheme } from '../../theme'; +import { isIOS } from '../../lib/methods/helpers'; const MAX_EMOJI_SIZE = 50; -const renderEmoji = (emoji: IEmoji, size: number, baseUrl: string) => { +interface IEmojiProps { + emoji: IEmoji; + size: number; + baseUrl: string; +} + +const Emoji = React.memo(({ emoji, size, baseUrl }: IEmojiProps) => { if (emoji?.isCustom || emoji?.name) { return ( { {shortnameToUnicode(`:${emoji}:`)} ); +}); + +const EmojiCategory = ({ + baseUrl, + onEmojiSelected, + emojis, + width, + tabsCount, + isBottomSheet +}: IEmojiCategory): React.ReactElement | null => { + const emojiSize = width ? Math.min(width / tabsCount, MAX_EMOJI_SIZE) : MAX_EMOJI_SIZE; + const numColumns = Math.trunc(width ? width / emojiSize : tabsCount); + const { colors } = useTheme(); + const FlatList = isBottomSheet ? BottomSheetFlatList : GHFlatList; + + const renderItem = (emoji: IEmoji) => ( + onEmojiSelected(emoji)} + testID={`reaction-picker-${emoji && emoji.isCustom ? emoji.content : emoji}`} + android_ripple={{ color: colors.bannerBackground, borderless: true, radius: emojiSize / 2 }} + style={({ pressed }: { pressed: boolean }) => ({ + backgroundColor: isIOS && pressed ? colors.bannerBackground : 'transparent' + })} + > + + + ); + + if (!width) { + return null; + } + + return ( + (item && item.isCustom && item.content) || item} + data={emojis} + extraData={{ baseUrl, width }} + renderItem={({ item }) => renderItem(item)} + numColumns={numColumns} + initialNumToRender={45} + removeClippedSubviews + {...scrollPersistTaps} + keyboardDismissMode={'none'} + /> + ); }; -const EmojiCategory = React.memo( - ({ baseUrl, onEmojiSelected, emojis, width, tabsCount, isBottomSheet, ...props }: IEmojiCategory) => { - const emojiSize = width ? Math.min(width / tabsCount, MAX_EMOJI_SIZE) : MAX_EMOJI_SIZE; - const numColumns = Math.trunc(width ? width / emojiSize : tabsCount); - - const FlatList = isBottomSheet ? BottomSheetFlatList : GHFlatList; - - const renderItem = (emoji: IEmoji) => ( - onEmojiSelected(emoji)} - testID={`reaction-picker-${emoji && emoji.isCustom ? emoji.content : emoji}`} - > - {renderEmoji(emoji, emojiSize, baseUrl)} - - ); - - if (!width) { - return null; - } - - return ( - (item && item.isCustom && item.content) || item} - data={emojis} - extraData={{ baseUrl, onEmojiSelected, width, ...props }} - renderItem={({ item }) => renderItem(item)} - numColumns={numColumns} - initialNumToRender={45} - removeClippedSubviews - {...scrollPersistTaps} - keyboardDismissMode={'none'} - /> - ); - } -); - export default EmojiCategory; diff --git a/app/containers/EmojiPicker/Footer.tsx b/app/containers/EmojiPicker/Footer.tsx index e2972cbda..0d433ca3e 100644 --- a/app/containers/EmojiPicker/Footer.tsx +++ b/app/containers/EmojiPicker/Footer.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { View, TouchableOpacity } from 'react-native'; +import { View, Pressable } from 'react-native'; import { BorderlessButton } from 'react-native-gesture-handler'; import { useTheme } from '../../theme'; @@ -14,12 +14,12 @@ const Footer = React.memo(({ onSearchPressed, onBackspacePressed }: IFooterProps return ( - + - + [{ opacity: pressed ? 0.7 : 1 }]}> - + ); }); diff --git a/app/containers/EmojiPicker/TabBar.tsx b/app/containers/EmojiPicker/TabBar.tsx index b80385eaa..22487f11d 100644 --- a/app/containers/EmojiPicker/TabBar.tsx +++ b/app/containers/EmojiPicker/TabBar.tsx @@ -1,28 +1,34 @@ import React from 'react'; -import { Text, TouchableOpacity, View } from 'react-native'; +import { Text, Pressable, View } from 'react-native'; import styles from './styles'; import { useTheme } from '../../theme'; import { ITabBarProps } from './interfaces'; +import { isIOS } from '../../lib/methods/helpers'; -const TabBar = React.memo(({ activeTab, tabs, goToPage, tabEmojiStyle }: ITabBarProps) => { +const TabBar = ({ activeTab, tabs, goToPage, tabEmojiStyle }: ITabBarProps): React.ReactElement => { const { colors } = useTheme(); return ( {tabs?.map((tab, i) => ( - goToPage?.(i)} - style={styles.tab} - testID={`reaction-picker-${tab}`}> + testID={`reaction-picker-${tab}`} + android_ripple={{ color: colors.bannerBackground }} + style={({ pressed }: { pressed: boolean }) => [ + styles.tab, + { + backgroundColor: isIOS && pressed ? colors.bannerBackground : 'transparent' + } + ]}> {tab} - + ))} ); -}); +}; export default TabBar; diff --git a/app/containers/EmojiPicker/index.tsx b/app/containers/EmojiPicker/index.tsx index 4cc48ba82..19cd6a39d 100644 --- a/app/containers/EmojiPicker/index.tsx +++ b/app/containers/EmojiPicker/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { View } from 'react-native'; import ScrollableTabView from 'react-native-scrollable-tab-view'; import orderBy from 'lodash/orderBy'; @@ -19,30 +19,47 @@ import { IEmoji, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definiti import { useAppSelector } from '../../lib/hooks'; import { IEmojiPickerProps, EventTypes } from './interfaces'; +const useFrequentlyUsedEmoji = () => { + const [frequentlyUsed, setFrequentlyUsed] = useState([]); + const [loaded, setLoaded] = useState(false); + const getFrequentlyUsedEmojis = async () => { + const db = database.active; + const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch(); + const frequentlyUsedOrdered = orderBy(frequentlyUsedRecords, ['count'], ['desc']); + const frequentlyUsedEmojis = frequentlyUsedOrdered.map(item => { + if (item.isCustom) { + return { content: item.content, extension: item.extension, isCustom: item.isCustom }; + } + return shortnameToUnicode(`${item.content}`); + }) as IEmoji[]; + setFrequentlyUsed(frequentlyUsedEmojis); + setLoaded(true); + }; + useEffect(() => { + getFrequentlyUsedEmojis(); + }, []); + return { frequentlyUsed, loaded }; +}; + const EmojiPicker = React.memo( ({ onItemClicked, tabEmojiStyle, isEmojiKeyboard = false, searching = false, searchedEmojis = [] }: IEmojiPickerProps) => { - const [frequentlyUsed, setFrequentlyUsed] = useState([]); - const [show, setShow] = useState(false); const [width, setWidth] = useState(null); const { colors } = useTheme(); + const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji(); const baseUrl = useAppSelector(state => state.server?.server); const allCustomEmojis: ICustomEmojis = useAppSelector(state => state.customEmojis); - const customEmojis = Object.keys(allCustomEmojis) - .filter(item => item === allCustomEmojis[item].name) - .map(item => ({ - content: allCustomEmojis[item].name, - extension: allCustomEmojis[item].extension, - isCustom: true - })); - - useEffect(() => { - const init = async () => { - await updateFrequentlyUsed(); - setShow(true); - }; - init(); - }, []); + const customEmojis = useMemo( + () => + Object.keys(allCustomEmojis) + .filter(item => item === allCustomEmojis[item].name) + .map(item => ({ + content: allCustomEmojis[item].name, + extension: allCustomEmojis[item].extension, + isCustom: true + })), + [allCustomEmojis] + ); const handleEmojiSelect = (emoji: IEmoji) => { try { @@ -91,19 +108,6 @@ const EmojiPicker = React.memo( }); }); - const updateFrequentlyUsed = async () => { - const db = database.active; - const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch(); - const frequentlyUsedOrdered = orderBy(frequentlyUsedRecords, ['count'], ['desc']); - const frequentlyUsedEmojis = frequentlyUsedOrdered.map(item => { - if (item.isCustom) { - return { content: item.content, extension: item.extension, isCustom: item.isCustom }; - } - return shortnameToUnicode(`${item.content}`); - }) as IEmoji[]; - setFrequentlyUsed(frequentlyUsedEmojis); - }; - const onLayout = ({ nativeEvent: { layout: { width } @@ -133,7 +137,7 @@ const EmojiPicker = React.memo( ); }; - if (!show) { + if (!loaded) { return null; } diff --git a/app/containers/EmojiPicker/styles.ts b/app/containers/EmojiPicker/styles.ts index 755da695e..065e2e03c 100644 --- a/app/containers/EmojiPicker/styles.ts +++ b/app/containers/EmojiPicker/styles.ts @@ -56,15 +56,15 @@ export default StyleSheet.create({ margin: 8 }, footerContainer: { - height: 45, + height: 44, paddingHorizontal: 12, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, footerButtonsContainer: { - height: 30, - width: 30, + height: 44, + width: 44, justifyContent: 'center', alignItems: 'center' } diff --git a/app/containers/MessageBox/EmojiSearchbar.tsx b/app/containers/MessageBox/EmojiSearchbar.tsx index f54b474ac..7c4289f65 100644 --- a/app/containers/MessageBox/EmojiSearchbar.tsx +++ b/app/containers/MessageBox/EmojiSearchbar.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { View, Text, TouchableOpacity, TextInput, FlatList } from 'react-native'; +import { View, Text, Pressable, TextInput, FlatList } from 'react-native'; import { orderBy } from 'lodash'; import { FormTextInput } from '../TextInput/FormTextInput'; @@ -63,9 +63,7 @@ const EmojiSearchbar = React.forwardRef( const emojiSize = 30; return ( - onEmojiSelected(emoji)}> - {renderEmoji(emoji, emojiSize, baseUrl)} - + onEmojiSelected(emoji)}>{renderEmoji(emoji, emojiSize, baseUrl)} ); }; @@ -87,9 +85,12 @@ const EmojiSearchbar = React.forwardRef( keyboardShouldPersistTaps='always' /> - + [styles.openEmojiKeyboard, { opacity: pressed ? 0.7 : 1 }]} + onPress={openEmoji} + hitSlop={BUTTON_HIT_SLOP}> - +