diff --git a/src/screens/searchResult/screen/searchResultScreen.tsx b/src/screens/searchResult/screen/searchResultScreen.tsx index f284454908..c53711e31c 100644 --- a/src/screens/searchResult/screen/searchResultScreen.tsx +++ b/src/screens/searchResult/screen/searchResultScreen.tsx @@ -1,11 +1,11 @@ -import React, { useState } from 'react'; +import React, { memo, useState } from 'react'; import { View } from 'react-native'; import ScrollableTabView from 'react-native-scrollable-tab-view'; import { useIntl } from 'react-intl'; -import { debounce } from 'lodash'; +import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; +import useDebounce from '../../../utils/useDebounceHook'; // Components -import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; import { SearchInput, TabBar } from '../../../components'; import Communities from './tabs/communities/view/communitiesResults'; import PostsResults from './tabs/best/view/postsResults'; @@ -17,13 +17,52 @@ import styles from './searchResultStyles'; import globalStyles from '../../../globalStyles'; const SearchResultScreen = ({ navigation }) => { - const [searchValue, setSearchValue] = useState(''); const intl = useIntl(); + const { debounce } = useDebounce(); + + const [searchInputValue, setSearchInputValue] = useState(''); + const [searchValue, setSearchValue] = useState(''); + + const _handleChangeText = (value) => { + setSearchInputValue(value); + }; + + const _handleSearchValue = (value) => { + setSearchValue(value); + }; + + // custom debounce to debounce search value but updates search input value instantly + // fixes character missing bug due to lodash debounce + const debouncedSearch = debounce(_handleSearchValue, _handleChangeText, 1000); const _navigationGoBack = () => { navigation.goBack(); }; + return ( + + + + + ); +}; + +const SearchResultsTabView = memo(({ searchValue }: { searchValue: string }) => { + const intl = useIntl(); + + const clippedSearchValue = + searchValue.startsWith('#') || searchValue.startsWith('@') + ? searchValue.substring(1) + : searchValue; + const isUsername = !!(searchValue.startsWith('#') || searchValue.startsWith('@')); + const _renderTabbar = () => ( { /> ); - const _handleChangeText = debounce((value) => { - setSearchValue(value); - }, 1000); - - const clippedSearchValue = - searchValue.startsWith('#') || searchValue.startsWith('@') - ? searchValue.substring(1) - : searchValue; - return ( - - - - + - - - - - - - - - - - - - - + + + + + + + + + + + + ); -}; +}); export default gestureHandlerRootHOC(SearchResultScreen); diff --git a/src/screens/searchResult/screen/tabs/people/container/peopleResultsContainer.js b/src/screens/searchResult/screen/tabs/people/container/peopleResultsContainer.js index 02b476b87c..334e450010 100644 --- a/src/screens/searchResult/screen/tabs/people/container/peopleResultsContainer.js +++ b/src/screens/searchResult/screen/tabs/people/container/peopleResultsContainer.js @@ -5,16 +5,24 @@ import { useNavigation } from '@react-navigation/native'; import ROUTES from '../../../../../../constants/routeNames'; import { searchAccount } from '../../../../../../providers/ecency/ecency'; +import { lookupAccounts } from '../../../../../../providers/hive/dhive'; -const PeopleResultsContainer = ({ children, searchValue, username }) => { +const PeopleResultsContainer = ({ children, searchValue, username, isUsername }) => { const navigation = useNavigation(); const [users, setUsers] = useState([]); + const [userNames, setUsernames] = useState([]); const [noResult, setNoResult] = useState(false); useEffect(() => { setNoResult(false); setUsers([]); + if (!searchValue) { + setUsernames([]); + } + if (searchValue && isUsername) { + _fetchUsernames(searchValue); + } searchAccount(searchValue, 20, searchValue ? 0 : 1) .then((res) => { @@ -29,6 +37,11 @@ const PeopleResultsContainer = ({ children, searchValue, username }) => { }); }, [searchValue]); + const _fetchUsernames = async (username) => { + const users = await lookupAccounts(username); + setUsernames(users); + }; + // Component Functions const _handleOnPress = (item) => { @@ -45,6 +58,7 @@ const PeopleResultsContainer = ({ children, searchValue, username }) => { children && children({ users, + userNames, handleOnPress: _handleOnPress, noResult, }) diff --git a/src/screens/searchResult/screen/tabs/people/view/peopleResults.js b/src/screens/searchResult/screen/tabs/people/view/peopleResults.js index 5d142b0d07..8f53e977e1 100644 --- a/src/screens/searchResult/screen/tabs/people/view/peopleResults.js +++ b/src/screens/searchResult/screen/tabs/people/view/peopleResults.js @@ -11,7 +11,7 @@ import PeopleResultsContainer from '../container/peopleResultsContainer'; import styles from './peopleResultsStyles'; -const PeopleResults = ({ searchValue }) => { +const PeopleResults = ({ searchValue, isUsername }) => { const _renderEmptyContent = () => { return ( <> @@ -20,11 +20,38 @@ const PeopleResults = ({ searchValue }) => { ); }; + const _renderUsernames = (userNames, handleOnPress) => { + return searchValue && isUsername && userNames && userNames.length ? ( + index.toString()} + renderItem={({ item, index }) => ( + + handleOnPress({ + name: item, + text: item, + }) + } + index={index} + username={item} + text={`@${item}`} + isHasRightItem + isLoggedIn + searchValue={searchValue} + isLoadingRightAction={false} + /> + )} + ListEmptyComponent={_renderEmptyContent} + /> + ) : null; + }; + return ( - - {({ users, handleOnPress, noResult }) => ( + + {({ users, userNames, handleOnPress, noResult }) => ( - {noResult ? ( + {noResult && !userNames.length ? ( ) : ( { /> )} ListEmptyComponent={_renderEmptyContent} + ListHeaderComponent={_renderUsernames(userNames, handleOnPress)} /> )} diff --git a/src/utils/useDebounceHook.ts b/src/utils/useDebounceHook.ts new file mode 100644 index 0000000000..c956648116 --- /dev/null +++ b/src/utils/useDebounceHook.ts @@ -0,0 +1,36 @@ +import { useEffect, useRef } from 'react'; + +/** + * custom debounce hook returns a method which debounces first method and always call the second method + */ +export const useDebounce = () => { + const timeoutToClear = useRef(null); + + useEffect(() => { + return () => { + clearTimeout(timeoutToClear?.current as any); + }; + }, []); + + /** + * custom debounce method which debounces first method and always call the second method + * @param {function which needs to be debounced} callback + * @param {this function would be called always} alwaysCall + * @param {debounce delay in ms} ms + */ + const debounce = (callback, alwaysCall, ms) => { + return (...args) => { + alwaysCall(...args); + clearTimeout(timeoutToClear?.current as any); + timeoutToClear.current = setTimeout(() => { + callback(...args); + }, ms); + }; + }; + + return { + debounce, + }; +}; + +export default useDebounce;