From 5e3cee079b08d9c944f3990556c7e401cd6fa1e7 Mon Sep 17 00:00:00 2001 From: Xc165543337 <90028194+Xc165543337@users.noreply.github.com> Date: Fri, 12 Apr 2024 10:29:23 +0200 Subject: [PATCH] perf: optimize icon selector --- .../HabitCreateForm/HabitCreateForm.tsx | 10 +- .../HabitCreateForm/HabitIconList.tsx | 56 +++++++ .../HabitIconSelectorModal.tsx | 151 ++++++++---------- 3 files changed, 126 insertions(+), 91 deletions(-) create mode 100644 presentation/react/components/HabitCreateForm/HabitIconList.tsx diff --git a/presentation/react/components/HabitCreateForm/HabitCreateForm.tsx b/presentation/react/components/HabitCreateForm/HabitCreateForm.tsx index 7b36efe..2e9d408 100644 --- a/presentation/react/components/HabitCreateForm/HabitCreateForm.tsx +++ b/presentation/react/components/HabitCreateForm/HabitCreateForm.tsx @@ -228,16 +228,8 @@ export const HabitCreateForm: React.FC = ({ user }) => { { + render={({ field: { onChange, value } }) => { return ( - // void +} + +const HabitIconListWithoutMemo: React.FC = (props) => { + const { selectedIcon, possibleIcons, handleIconSelect } = props + if (possibleIcons.length > 0) { + return ( + + {possibleIcons.map((icon) => { + return ( + { + return ( + + ) + }} + size={30} + onPress={() => { + handleIconSelect(icon) + }} + /> + ) + })} + + ) + } + return ( + + No results found + + ) +} + +const styles = StyleSheet.create({ + noResults: { + marginTop: 20, + alignItems: "center", + }, +}) + +export const HabitIconList = memo(HabitIconListWithoutMemo) diff --git a/presentation/react/components/HabitCreateForm/HabitIconSelectorModal.tsx b/presentation/react/components/HabitCreateForm/HabitIconSelectorModal.tsx index 90f558b..be0fcc9 100644 --- a/presentation/react/components/HabitCreateForm/HabitIconSelectorModal.tsx +++ b/presentation/react/components/HabitCreateForm/HabitIconSelectorModal.tsx @@ -1,22 +1,16 @@ -import React, { useState, useEffect } from "react" -import { - Modal, - IconButton, - Portal, - List, - Button, - TextInput, - Text, -} from "react-native-paper" -import { ScrollView, View, StyleSheet } from "react-native" -import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome" -import type { - IconDefinition, - IconName, -} from "@fortawesome/fontawesome-svg-core" -import { library, findIconDefinition } from "@fortawesome/fontawesome-svg-core" +import { library } from "@fortawesome/fontawesome-svg-core" import { fas } from "@fortawesome/free-solid-svg-icons" +import React, { + memo, + useCallback, + useEffect, + useState, + useTransition, +} from "react" +import { ScrollView, StyleSheet, View } from "react-native" +import { Button, List, Modal, Portal, TextInput } from "react-native-paper" +import { HabitIconList } from "./HabitIconList" export interface HabitIconSelectorModalProps { visible?: boolean value: string @@ -25,6 +19,34 @@ export interface HabitIconSelectorModalProps { onPress: () => void } +interface SearchInputProps { + searchText: string + handleSearch: (text: string) => void +} + +library.add(fas) + +const iconNames = Object.keys(fas).map((key) => { + return fas[key]?.iconName ?? "" +}) + +const findIconsInLibrary = (icon: string): string[] => { + return iconNames + .filter((name, index, self) => { + return name.includes(icon) && self.indexOf(name) === index + }) + .slice(0, 20) +} + +const SearchInputWithoutMemo: React.FC = (props) => { + const { searchText, handleSearch } = props + return ( + + ) +} + +const SearchInput = memo(SearchInputWithoutMemo) + export const HabitIconSelectorModal: React.FC = ({ visible = false, value, @@ -33,36 +55,32 @@ export const HabitIconSelectorModal: React.FC = ({ onPress, }) => { const [selectedIcon, setSelectedIcon] = useState() - const [possibleIcons, setPossibleIcons] = useState([]) + const [possibleIcons, setPossibleIcons] = useState([]) const [searchText, setSearchText] = useState(value) + const [isPending, startTransition] = useTransition() + + const handleSearch = useCallback((text: string): void => { + setSearchText(text) + }, []) useEffect(() => { - setPossibleIcons(findIconsInLibrary(searchText)) - }, [searchText]) + const delay = setTimeout(() => { + startTransition(() => { + setPossibleIcons(findIconsInLibrary(searchText)) + }) + }, 500) + return () => { + return clearTimeout(delay) + } + }, [searchText, isPending]) - library.add(fas) - - const handleIconSelect = (icon: string): void => { - setSelectedIcon(icon) - onSelect(icon) - } - - const findIconsInLibrary = (icon: string): IconDefinition[] => { - const iconNames = Object.keys(fas).map((key) => { - return fas[key]?.iconName ?? "" - }) - const matchedValue: string[] = iconNames.filter((name) => { - return name.includes(icon) - }) - return matchedValue.length > 0 - ? matchedValue.map((name) => { - return findIconDefinition({ - prefix: "fas", - iconName: name as IconName, - }) - }) - : [] - } + const handleIconSelect = useCallback( + (icon: string): void => { + setSelectedIcon(icon) + onSelect(icon) + }, + [onSelect], + ) return ( <> @@ -77,46 +95,15 @@ export const HabitIconSelectorModal: React.FC = ({ - { - return setSearchText(text) - }} + + - {possibleIcons.length > 0 ? ( - - {possibleIcons.map((icon) => { - return ( - { - return ( - - ) - }} - size={30} - onPress={() => { - handleIconSelect(icon.iconName) - }} - /> - ) - })} - - ) : ( - - No results found - - )}