diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 01fe83289d71..d779ab856fb5 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -1,17 +1,14 @@ import PropTypes from 'prop-types'; -import React, {Component} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import networkPropTypes from '@components/networkPropTypes'; -import {withNetwork} from '@components/OnyxProvider'; import OptionsSelector from '@components/OptionsSelector'; import ScreenWrapper from '@components/ScreenWrapper'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeStyles'; -import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; -import compose from '@libs/compose'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; +import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import Performance from '@libs/Performance'; @@ -35,207 +32,195 @@ const propTypes = { /** All reports shared with the user */ reports: PropTypes.objectOf(reportPropTypes), - /** Window Dimensions Props */ - ...windowDimensionsPropTypes, - - ...withLocalizePropTypes, - - /** Network info */ - network: networkPropTypes, - /** Whether we are searching for reports in the server */ isSearchingForReports: PropTypes.bool, - ...withThemeStylesPropTypes, }; const defaultProps = { betas: [], personalDetails: {}, reports: {}, - network: {}, isSearchingForReports: false, }; -class SearchPage extends Component { - constructor(props) { - super(props); +function SearchPage({betas, personalDetails, reports, isSearchingForReports}) { + const [searchValue, setSearchValue] = useState(''); + const [searchOptions, setSearchOptions] = useState(() => { + const { + recentReports: localRecentReports, + personalDetails: localPersonalDetails, + userToInvite: localUserToInvite, + } = OptionsListUtils.getSearchOptions(reports, personalDetails, '', betas); + + return { + recentReports: localRecentReports, + personalDetails: localPersonalDetails, + userToInvite: localUserToInvite, + }; + }); + + const {isOffline} = useNetwork(); + const {translate} = useLocalize(); + const themeStyles = useThemeStyles(); + const isMounted = useRef(false); + + const updateOptions = useCallback(() => { + const { + recentReports: localRecentReports, + personalDetails: localPersonalDetails, + userToInvite: localUserToInvite, + } = OptionsListUtils.getSearchOptions(reports, personalDetails, searchValue.trim(), betas); + + setSearchOptions({ + recentReports: localRecentReports, + personalDetails: localPersonalDetails, + userToInvite: localUserToInvite, + }); + }, [reports, personalDetails, searchValue, betas]); + + const debouncedUpdateOptions = useMemo(() => _.debounce(updateOptions, 75), [updateOptions]); + useEffect(() => { Timing.start(CONST.TIMING.SEARCH_RENDER); Performance.markStart(CONST.TIMING.SEARCH_RENDER); + }, []); - this.searchRendered = this.searchRendered.bind(this); - this.selectReport = this.selectReport.bind(this); - this.onChangeText = this.onChangeText.bind(this); - this.updateOptions = this.updateOptions.bind(this); - this.debouncedUpdateOptions = _.debounce(this.updateOptions.bind(this), 75); - this.state = { - searchValue: '', - recentReports: {}, - personalDetails: {}, - userToInvite: {}, - }; - } + useEffect(() => { + updateOptions(); + }, [reports, personalDetails, betas, updateOptions]); - componentDidUpdate(prevProps) { - if (_.isEqual(prevProps.reports, this.props.reports) && _.isEqual(prevProps.personalDetails, this.props.personalDetails)) { + useEffect(() => { + if (!isMounted.current) { + isMounted.current = true; return; } - this.updateOptions(); - } - onChangeText(searchValue = '') { - Report.searchInServer(searchValue); - this.setState({searchValue}, this.debouncedUpdateOptions); - } + debouncedUpdateOptions(); + // Ignoring the rule intentionally, we want to run the code only when search Value changes to prevent additional runs. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [searchValue]); /** * Returns the sections needed for the OptionsSelector * * @returns {Array} */ - getSections() { + const getSections = () => { const sections = []; let indexOffset = 0; - if (this.state.recentReports.length > 0) { + if (searchOptions.recentReports.length > 0) { sections.push({ - data: this.state.recentReports, + data: searchOptions.recentReports, shouldShow: true, indexOffset, }); - indexOffset += this.state.recentReports.length; + indexOffset += searchOptions.recentReports.length; } - if (this.state.personalDetails.length > 0) { + if (searchOptions.personalDetails.length > 0) { sections.push({ - data: this.state.personalDetails, + data: searchOptions.personalDetails, shouldShow: true, indexOffset, }); - indexOffset += this.state.recentReports.length; + indexOffset += searchOptions.recentReports.length; } - if (this.state.userToInvite) { + if (searchOptions.userToInvite) { sections.push({ - data: [this.state.userToInvite], + data: [searchOptions.userToInvite], shouldShow: true, indexOffset, }); } return sections; - } + }; - searchRendered() { + const searchRendered = () => { Timing.end(CONST.TIMING.SEARCH_RENDER); Performance.markEnd(CONST.TIMING.SEARCH_RENDER); - } - - updateOptions() { - const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getSearchOptions( - this.props.reports, - this.props.personalDetails, - this.state.searchValue.trim(), - this.props.betas, - ); - this.setState({ - userToInvite, - recentReports, - personalDetails, - }); - } + }; + + const onChangeText = (value = '') => { + Report.searchInServer(searchValue); + setSearchValue(value); + }; /** * Reset the search value and redirect to the selected report * * @param {Object} option */ - selectReport(option) { + const selectReport = (option) => { if (!option) { return; } - if (option.reportID) { - this.setState( - { - searchValue: '', - }, - () => { - Navigation.dismissModal(option.reportID); - }, - ); + Navigation.dismissModal(option.reportID); } else { Report.navigateToAndOpenReport([option.login]); } - } - - render() { - const sections = this.getSections(); - const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(this.props.personalDetails); - const headerMessage = OptionsListUtils.getHeaderMessage( - this.state.recentReports.length + this.state.personalDetails.length !== 0, - Boolean(this.state.userToInvite), - this.state.searchValue, - ); - - return ( - - {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( - <> - - - - - - )} - - ); - } + }; + + const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails); + const headerMessage = OptionsListUtils.getHeaderMessage( + searchOptions.recentReports.length + searchOptions.personalDetails.length !== 0, + Boolean(searchOptions.userToInvite), + searchValue, + ); + + return ( + + {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( + <> + + + + + + )} + + ); } SearchPage.propTypes = propTypes; SearchPage.defaultProps = defaultProps; SearchPage.displayName = 'SearchPage'; - -export default compose( - withLocalize, - withWindowDimensions, - withNetwork(), - withOnyx({ - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - betas: { - key: ONYXKEYS.BETAS, - }, - isSearchingForReports: { - key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, - initWithStoredValues: false, - }, - }), - withThemeStyles, -)(SearchPage); +export default withOnyx({ + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + betas: { + key: ONYXKEYS.BETAS, + }, + isSearchingForReports: { + key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, + initWithStoredValues: false, + }, +})(SearchPage);