-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Convert SearchPage to functional component #23076
Changes from 1 commit
1a892d2
2d2676e
804cc20
d49f271
c187f69
718fcb9
4b47c24
e502530
6f46693
6024bce
22042e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,5 +1,5 @@ | ||||||
import _ from 'underscore'; | ||||||
import React, {Component} from 'react'; | ||||||
import React, {useCallback, useEffect, useState} from 'react'; | ||||||
import {View} from 'react-native'; | ||||||
import PropTypes from 'prop-types'; | ||||||
import {withOnyx} from 'react-native-onyx'; | ||||||
|
@@ -45,69 +45,68 @@ const defaultProps = { | |||||
reports: {}, | ||||||
}; | ||||||
|
||||||
class SearchPage extends Component { | ||||||
constructor(props) { | ||||||
super(props); | ||||||
function SearchPage(props) { | ||||||
const {recentReports: initialRecentReports, personalDetails: initialPersonalDetails, userToInvite: initialUserToInvite} = OptionsListUtils.getSearchOptions(props.reports, props.personalDetails, '', props.betas); | ||||||
Piotrfj marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
Timing.start(CONST.TIMING.SEARCH_RENDER); | ||||||
Performance.markStart(CONST.TIMING.SEARCH_RENDER); | ||||||
const [searchValue, setSearchValue] = useState('') | ||||||
const [recentReports, setRecentReports] = useState(initialRecentReports) | ||||||
const [personalDetails, setPersonalDetails] = useState(initialPersonalDetails) | ||||||
const [userToInvite, setUserToInvite] = useState(initialUserToInvite) | ||||||
|
||||||
this.searchRendered = this.searchRendered.bind(this); | ||||||
this.selectReport = this.selectReport.bind(this); | ||||||
this.onChangeText = this.onChangeText.bind(this); | ||||||
this.debouncedUpdateOptions = _.debounce(this.updateOptions.bind(this), 75); | ||||||
const updateOptions = useCallback(() => { | ||||||
const {recentReports: localRecentReports, personalDetails: localPersonalDetails, userToInvite: localUserToInvite} = OptionsListUtils.getSearchOptions( | ||||||
props.reports, | ||||||
props.personalDetails, | ||||||
searchValue.trim(), | ||||||
props.betas, | ||||||
); | ||||||
|
||||||
const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getSearchOptions(props.reports, props.personalDetails, '', props.betas); | ||||||
setUserToInvite(localUserToInvite) | ||||||
setRecentReports(localRecentReports) | ||||||
setPersonalDetails(localPersonalDetails) | ||||||
}, [props.reports, props.personalDetails, searchValue, props.betas]) | ||||||
|
||||||
this.state = { | ||||||
searchValue: '', | ||||||
recentReports, | ||||||
personalDetails, | ||||||
userToInvite, | ||||||
}; | ||||||
} | ||||||
const debouncedUpdateOptions =_.debounce(updateOptions, 75); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the app should have the same reference across the re-renders. useCallback is necessary here. |
||||||
|
||||||
componentDidUpdate(prevProps) { | ||||||
if (_.isEqual(prevProps.reports, this.props.reports) && _.isEqual(prevProps.personalDetails, this.props.personalDetails)) { | ||||||
return; | ||||||
} | ||||||
this.updateOptions(); | ||||||
} | ||||||
useEffect(() => { | ||||||
Timing.start(CONST.TIMING.SEARCH_RENDER); | ||||||
Performance.markStart(CONST.TIMING.SEARCH_RENDER); | ||||||
},[]) | ||||||
|
||||||
onChangeText(searchValue = '') { | ||||||
this.setState({searchValue}, this.debouncedUpdateOptions); | ||||||
} | ||||||
useEffect(() => { | ||||||
updateOptions(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gedu, wdyt about the next option - https://github.com/Expensify/App/pull/23076/files#r1267798688? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just realized that |
||||||
},[props.reports, props.personalDetails]) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this
Suggested change
|
||||||
|
||||||
/** | ||||||
* Returns the sections needed for the OptionsSelector | ||||||
* | ||||||
* @returns {Array} | ||||||
*/ | ||||||
getSections() { | ||||||
const getSections = () => { | ||||||
const sections = []; | ||||||
let indexOffset = 0; | ||||||
|
||||||
if (this.state.recentReports.length > 0) { | ||||||
if (recentReports.length > 0) { | ||||||
sections.push({ | ||||||
data: this.state.recentReports, | ||||||
data: recentReports, | ||||||
shouldShow: true, | ||||||
indexOffset, | ||||||
}); | ||||||
indexOffset += this.state.recentReports.length; | ||||||
indexOffset += recentReports.length; | ||||||
} | ||||||
|
||||||
if (this.state.personalDetails.length > 0) { | ||||||
if (personalDetails.length > 0) { | ||||||
sections.push({ | ||||||
data: this.state.personalDetails, | ||||||
data: personalDetails, | ||||||
shouldShow: true, | ||||||
indexOffset, | ||||||
}); | ||||||
indexOffset += this.state.recentReports.length; | ||||||
indexOffset += recentReports.length; | ||||||
} | ||||||
|
||||||
if (this.state.userToInvite) { | ||||||
if (userToInvite) { | ||||||
sections.push({ | ||||||
data: [this.state.userToInvite], | ||||||
data: [userToInvite], | ||||||
shouldShow: true, | ||||||
indexOffset, | ||||||
}); | ||||||
|
@@ -116,88 +115,71 @@ class SearchPage extends Component { | |||||
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, | ||||||
}); | ||||||
useEffect(() => { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please group all |
||||||
debouncedUpdateOptions() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are you updating the options after every new word? You are listening to reports and personalDetails to update the options |
||||||
}, [searchValue]) | ||||||
|
||||||
const onChangeText = (value = '') => { | ||||||
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); | ||||||
}, | ||||||
); | ||||||
setSearchValue('') | ||||||
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 ( | ||||||
<ScreenWrapper includeSafeAreaPaddingBottom={false}> | ||||||
{({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( | ||||||
<> | ||||||
<HeaderWithBackButton title={this.props.translate('common.search')} /> | ||||||
<View style={[styles.flex1, styles.w100, styles.pRelative]}> | ||||||
<OptionsSelector | ||||||
sections={sections} | ||||||
value={this.state.searchValue} | ||||||
onSelectRow={this.selectReport} | ||||||
onChangeText={this.onChangeText} | ||||||
headerMessage={headerMessage} | ||||||
hideSectionHeaders | ||||||
showTitleTooltip | ||||||
shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} | ||||||
textInputLabel={this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} | ||||||
onLayout={this.searchRendered} | ||||||
safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} | ||||||
/> | ||||||
</View> | ||||||
</> | ||||||
)} | ||||||
</ScreenWrapper> | ||||||
); | ||||||
} | ||||||
const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(props.personalDetails); | ||||||
const headerMessage = OptionsListUtils.getHeaderMessage( | ||||||
recentReports.length + personalDetails.length !== 0, | ||||||
Boolean(userToInvite), | ||||||
searchValue, | ||||||
); | ||||||
return ( | ||||||
<ScreenWrapper includeSafeAreaPaddingBottom={false}> | ||||||
{({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( | ||||||
<> | ||||||
<HeaderWithBackButton title={props.translate('common.search')} /> | ||||||
<View style={[styles.flex1, styles.w100, styles.pRelative]}> | ||||||
<OptionsSelector | ||||||
sections={getSections()} | ||||||
value={searchValue} | ||||||
onSelectRow={selectReport} | ||||||
onChangeText={onChangeText} | ||||||
headerMessage={headerMessage} | ||||||
hideSectionHeaders | ||||||
showTitleTooltip | ||||||
shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} | ||||||
textInputLabel={props.translate('optionsSelector.nameEmailOrPhoneNumber')} | ||||||
onLayout={searchRendered} | ||||||
safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} | ||||||
/> | ||||||
</View> | ||||||
</> | ||||||
)} | ||||||
</ScreenWrapper> | ||||||
) | ||||||
} | ||||||
|
||||||
SearchPage.propTypes = propTypes; | ||||||
SearchPage.defaultProps = defaultProps; | ||||||
|
||||||
SearchPage.displayName = 'SearchPage'; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use hooks |
||||||
export default compose( | ||||||
withLocalize, | ||||||
withWindowDimensions, | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use prop destructuring now in order to be aligned with the style guidelines -> https://github.com/Expensify/App/blob/main/contributingGuides/STYLE.md#destructuring
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, you have to remove usage ofdefaultProps
and assign default values during the prop destructuring.You don't need to do this one now, we should still use
defaultProps
in JS files.