-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Migrate NewChatPage to functional component #20170
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
Changes from all commits
c7b9245
a357a85
13d2e60
8351d34
a75802f
8115857
51cb0b8
f213e8f
80a9a9b
56b5491
faf1a01
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, {useState, useEffect, useMemo} from 'react'; | ||||||
| import {View} from 'react-native'; | ||||||
| import PropTypes from 'prop-types'; | ||||||
| import {withOnyx} from 'react-native-onyx'; | ||||||
|
|
@@ -44,148 +44,98 @@ const defaultProps = { | |||||
| reports: {}, | ||||||
| }; | ||||||
|
|
||||||
| class NewChatPage extends Component { | ||||||
| constructor(props) { | ||||||
| super(props); | ||||||
|
|
||||||
| this.toggleOption = this.toggleOption.bind(this); | ||||||
| this.createChat = this.createChat.bind(this); | ||||||
| this.createGroup = this.createGroup.bind(this); | ||||||
| this.updateOptionsWithSearchTerm = this.updateOptionsWithSearchTerm.bind(this); | ||||||
| this.excludedGroupEmails = _.without(CONST.EXPENSIFY_EMAILS, CONST.EMAIL.CONCIERGE); | ||||||
|
|
||||||
| const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( | ||||||
| props.reports, | ||||||
| props.personalDetails, | ||||||
| props.betas, | ||||||
| '', | ||||||
| [], | ||||||
| this.props.isGroupChat ? this.excludedGroupEmails : [], | ||||||
| ); | ||||||
| this.state = { | ||||||
| searchTerm: '', | ||||||
| recentReports, | ||||||
| personalDetails, | ||||||
| selectedOptions: [], | ||||||
| userToInvite, | ||||||
| }; | ||||||
| } | ||||||
|
|
||||||
| componentDidUpdate(prevProps) { | ||||||
| if (_.isEqual(prevProps.reports, this.props.reports) && _.isEqual(prevProps.personalDetails, this.props.personalDetails)) { | ||||||
| return; | ||||||
| } | ||||||
| this.updateOptionsWithSearchTerm(this.state.searchTerm); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Returns the sections needed for the OptionsSelector | ||||||
| * | ||||||
| * @param {Boolean} maxParticipantsReached | ||||||
| * @returns {Array} | ||||||
| */ | ||||||
| getSections(maxParticipantsReached) { | ||||||
| const sections = []; | ||||||
| const excludedGroupEmails = _.without(CONST.EXPENSIFY_EMAILS, CONST.EMAIL.CONCIERGE); | ||||||
|
|
||||||
| function NewChatPage(props) { | ||||||
| const [searchTerm, setSearchTerm] = useState(''); | ||||||
| const [filteredRecentReports, setFilteredRecentReports] = useState([]); | ||||||
| const [filteredPersonalDetails, setFilteredPersonalDetails] = useState([]); | ||||||
| const [filteredUserToInvite, setFilteredUserToInvite] = useState(); | ||||||
| const [selectedOptions, setSelectedOptions] = useState([]); | ||||||
|
|
||||||
| const maxParticipantsReached = selectedOptions.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; | ||||||
| const headerMessage = OptionsListUtils.getHeaderMessage( | ||||||
| filteredPersonalDetails.length + filteredRecentReports.length !== 0, | ||||||
| Boolean(filteredUserToInvite), | ||||||
| searchTerm, | ||||||
| maxParticipantsReached, | ||||||
| ); | ||||||
| const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(props.personalDetails); | ||||||
|
|
||||||
| const sections = useMemo(() => { | ||||||
|
Contributor
Author
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 used
But I propose to remove it following the guidelines in React docs -
2 and 3 are not applicable here. For the first point, the dependencies change often in our case which would anyway recalculate the value. If you agree then we can remove it from
Contributor
Author
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. discussion on useCallback, useMemo in slack
Contributor
Author
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. @aimane-chnaif I will remove useMemo here if you agree.
Contributor
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 so. I don't see much benefit of performance improvement using |
||||||
| const sectionsList = []; | ||||||
| let indexOffset = 0; | ||||||
|
|
||||||
| if (this.props.isGroupChat) { | ||||||
| sections.push({ | ||||||
| if (props.isGroupChat) { | ||||||
| sectionsList.push({ | ||||||
| title: undefined, | ||||||
| data: this.state.selectedOptions, | ||||||
| shouldShow: !_.isEmpty(this.state.selectedOptions), | ||||||
| data: selectedOptions, | ||||||
| shouldShow: !_.isEmpty(selectedOptions), | ||||||
| indexOffset, | ||||||
| }); | ||||||
| indexOffset += this.state.selectedOptions.length; | ||||||
| indexOffset += selectedOptions.length; | ||||||
|
|
||||||
| if (maxParticipantsReached) { | ||||||
| return sections; | ||||||
| return sectionsList; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // Filtering out selected users from the search results | ||||||
| const filterText = _.reduce(this.state.selectedOptions, (str, {login}) => `${str} ${login}`, ''); | ||||||
| const recentReportsWithoutSelected = _.filter(this.state.recentReports, ({login}) => !filterText.includes(login)); | ||||||
| const personalDetailsWithoutSelected = _.filter(this.state.personalDetails, ({login}) => !filterText.includes(login)); | ||||||
| const hasUnselectedUserToInvite = this.state.userToInvite && !filterText.includes(this.state.userToInvite.login); | ||||||
| const filterText = _.reduce(selectedOptions, (str, {login}) => `${str} ${login}`, ''); | ||||||
| const recentReportsWithoutSelected = _.filter(filteredRecentReports, ({login}) => !filterText.includes(login)); | ||||||
| const personalDetailsWithoutSelected = _.filter(filteredPersonalDetails, ({login}) => !filterText.includes(login)); | ||||||
| const hasUnselectedUserToInvite = filteredUserToInvite && !filterText.includes(filteredUserToInvite.login); | ||||||
|
|
||||||
| sections.push({ | ||||||
| title: this.props.translate('common.recents'), | ||||||
| sectionsList.push({ | ||||||
| title: props.translate('common.recents'), | ||||||
| data: recentReportsWithoutSelected, | ||||||
| shouldShow: !_.isEmpty(recentReportsWithoutSelected), | ||||||
| indexOffset, | ||||||
| }); | ||||||
| indexOffset += recentReportsWithoutSelected.length; | ||||||
|
|
||||||
| sections.push({ | ||||||
| title: this.props.translate('common.contacts'), | ||||||
| sectionsList.push({ | ||||||
| title: props.translate('common.contacts'), | ||||||
| data: personalDetailsWithoutSelected, | ||||||
| shouldShow: !_.isEmpty(personalDetailsWithoutSelected), | ||||||
| indexOffset, | ||||||
| }); | ||||||
| indexOffset += personalDetailsWithoutSelected.length; | ||||||
|
|
||||||
| if (hasUnselectedUserToInvite) { | ||||||
| sections.push({ | ||||||
| sectionsList.push({ | ||||||
| title: undefined, | ||||||
| data: [this.state.userToInvite], | ||||||
| data: [filteredUserToInvite], | ||||||
| shouldShow: true, | ||||||
| indexOffset, | ||||||
| }); | ||||||
| } | ||||||
|
|
||||||
| return sections; | ||||||
| } | ||||||
|
|
||||||
| updateOptionsWithSearchTerm(searchTerm = '') { | ||||||
| const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( | ||||||
| this.props.reports, | ||||||
| this.props.personalDetails, | ||||||
| this.props.betas, | ||||||
| searchTerm, | ||||||
| [], | ||||||
| this.props.isGroupChat ? this.excludedGroupEmails : [], | ||||||
| ); | ||||||
| this.setState({ | ||||||
| searchTerm, | ||||||
| userToInvite, | ||||||
| recentReports, | ||||||
| personalDetails, | ||||||
| }); | ||||||
| } | ||||||
| return sectionsList; | ||||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||||||
| }, [filteredPersonalDetails, filteredRecentReports, filteredUserToInvite, maxParticipantsReached, props.isGroupChat, selectedOptions]); | ||||||
|
|
||||||
| /** | ||||||
| * Removes a selected option from list if already selected. If not already selected add this option to the list. | ||||||
| * @param {Object} option | ||||||
| */ | ||||||
| toggleOption(option) { | ||||||
| this.setState((prevState) => { | ||||||
| const isOptionInList = _.some(prevState.selectedOptions, (selectedOption) => selectedOption.login === option.login); | ||||||
| function toggleOption(option) { | ||||||
| const isOptionInList = _.some(selectedOptions, (selectedOption) => selectedOption.login === option.login); | ||||||
|
|
||||||
| let newSelectedOptions; | ||||||
| let newSelectedOptions; | ||||||
|
|
||||||
| if (isOptionInList) { | ||||||
| newSelectedOptions = _.reject(prevState.selectedOptions, (selectedOption) => selectedOption.login === option.login); | ||||||
| } else { | ||||||
| newSelectedOptions = [...prevState.selectedOptions, option]; | ||||||
| } | ||||||
| if (isOptionInList) { | ||||||
| newSelectedOptions = _.reject(selectedOptions, (selectedOption) => selectedOption.login === option.login); | ||||||
| } else { | ||||||
| newSelectedOptions = [...selectedOptions, option]; | ||||||
| } | ||||||
|
|
||||||
| const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( | ||||||
| this.props.reports, | ||||||
| this.props.personalDetails, | ||||||
| this.props.betas, | ||||||
| prevState.searchTerm, | ||||||
| [], | ||||||
| this.excludedGroupEmails, | ||||||
| ); | ||||||
| const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions(props.reports, props.personalDetails, props.betas, searchTerm, [], excludedGroupEmails); | ||||||
|
|
||||||
| return { | ||||||
| selectedOptions: newSelectedOptions, | ||||||
| recentReports, | ||||||
| personalDetails, | ||||||
| userToInvite, | ||||||
| searchTerm: prevState.searchTerm, | ||||||
| }; | ||||||
| }); | ||||||
| setSelectedOptions(newSelectedOptions); | ||||||
| setFilteredRecentReports(recentReports); | ||||||
| setFilteredPersonalDetails(personalDetails); | ||||||
| setFilteredUserToInvite(userToInvite); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
|
|
@@ -194,73 +144,77 @@ class NewChatPage extends Component { | |||||
| * | ||||||
| * @param {Object} option | ||||||
| */ | ||||||
| createChat(option) { | ||||||
| function createChat(option) { | ||||||
| Report.navigateToAndOpenReport([option.login]); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Creates a new group chat with all the selected options and the current user, | ||||||
| * or navigates to the existing chat if one with those participants already exists. | ||||||
| */ | ||||||
| createGroup() { | ||||||
| if (!this.props.isGroupChat) { | ||||||
| const createGroup = () => { | ||||||
|
Contributor
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. Same here
Contributor
Author
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. didn't use function here as it was passed down as prop |
||||||
| if (!props.isGroupChat) { | ||||||
| return; | ||||||
| } | ||||||
| const logins = _.pluck(this.state.selectedOptions, 'login'); | ||||||
| const logins = _.pluck(selectedOptions, 'login'); | ||||||
| if (logins.length < 1) { | ||||||
| return; | ||||||
| } | ||||||
| Report.navigateToAndOpenReport(logins); | ||||||
| } | ||||||
| }; | ||||||
|
|
||||||
| render() { | ||||||
| const maxParticipantsReached = this.state.selectedOptions.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; | ||||||
| const sections = this.getSections(maxParticipantsReached); | ||||||
| const headerMessage = OptionsListUtils.getHeaderMessage( | ||||||
| this.state.personalDetails.length + this.state.recentReports.length !== 0, | ||||||
| Boolean(this.state.userToInvite), | ||||||
| this.state.searchTerm, | ||||||
| maxParticipantsReached, | ||||||
| ); | ||||||
| const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(this.props.personalDetails); | ||||||
|
|
||||||
| return ( | ||||||
| <ScreenWrapper | ||||||
| includeSafeAreaPaddingBottom={false} | ||||||
| shouldEnableMaxHeight | ||||||
| > | ||||||
| {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( | ||||||
| <> | ||||||
| <HeaderWithBackButton title={this.props.isGroupChat ? this.props.translate('sidebarScreen.newGroup') : this.props.translate('sidebarScreen.newChat')} /> | ||||||
|
|
||||||
| <View style={[styles.flex1, styles.w100, styles.pRelative, this.state.selectedOptions.length > 0 ? safeAreaPaddingBottomStyle : {}]}> | ||||||
| <OptionsSelector | ||||||
| canSelectMultipleOptions={this.props.isGroupChat} | ||||||
| sections={sections} | ||||||
| selectedOptions={this.state.selectedOptions} | ||||||
| value={this.state.searchTerm} | ||||||
| onSelectRow={(option) => (this.props.isGroupChat ? this.toggleOption(option) : this.createChat(option))} | ||||||
| onChangeText={this.updateOptionsWithSearchTerm} | ||||||
| headerMessage={headerMessage} | ||||||
| boldStyle | ||||||
| shouldFocusOnSelectRow={this.props.isGroupChat && !Browser.isMobile()} | ||||||
| shouldShowConfirmButton={this.props.isGroupChat} | ||||||
| shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} | ||||||
| confirmButtonText={this.props.translate('newChatPage.createGroup')} | ||||||
| onConfirmSelection={this.createGroup} | ||||||
| textInputLabel={this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} | ||||||
| safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} | ||||||
| /> | ||||||
| </View> | ||||||
| </> | ||||||
| )} | ||||||
| </ScreenWrapper> | ||||||
| useEffect(() => { | ||||||
| const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( | ||||||
| props.reports, | ||||||
| props.personalDetails, | ||||||
| props.betas, | ||||||
| searchTerm, | ||||||
| [], | ||||||
| props.isGroupChat ? excludedGroupEmails : [], | ||||||
| ); | ||||||
| } | ||||||
| setFilteredRecentReports(recentReports); | ||||||
| setFilteredPersonalDetails(personalDetails); | ||||||
| setFilteredUserToInvite(userToInvite); | ||||||
| // props.betas and props.isGroupChat are not added as dependencies since they don't change during the component lifecycle | ||||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||||||
| }, [props.reports, props.personalDetails, searchTerm]); | ||||||
|
|
||||||
| return ( | ||||||
| <ScreenWrapper | ||||||
| includeSafeAreaPaddingBottom={false} | ||||||
| shouldEnableMaxHeight | ||||||
| > | ||||||
| {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( | ||||||
| <> | ||||||
| <HeaderWithBackButton title={props.isGroupChat ? props.translate('sidebarScreen.newGroup') : props.translate('sidebarScreen.newChat')} /> | ||||||
| <View style={[styles.flex1, styles.w100, styles.pRelative, selectedOptions.length > 0 ? safeAreaPaddingBottomStyle : {}]}> | ||||||
| <OptionsSelector | ||||||
| canSelectMultipleOptions={props.isGroupChat} | ||||||
| sections={sections} | ||||||
| selectedOptions={selectedOptions} | ||||||
| value={searchTerm} | ||||||
| onSelectRow={(option) => (props.isGroupChat ? toggleOption(option) : createChat(option))} | ||||||
| onChangeText={setSearchTerm} | ||||||
| headerMessage={headerMessage} | ||||||
| boldStyle | ||||||
| shouldFocusOnSelectRow={props.isGroupChat && !Browser.isMobile()} | ||||||
| shouldShowConfirmButton={props.isGroupChat} | ||||||
| shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} | ||||||
| confirmButtonText={props.translate('newChatPage.createGroup')} | ||||||
| onConfirmSelection={createGroup} | ||||||
| textInputLabel={props.translate('optionsSelector.nameEmailOrPhoneNumber')} | ||||||
| safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} | ||||||
| /> | ||||||
| </View> | ||||||
| </> | ||||||
| )} | ||||||
| </ScreenWrapper> | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| NewChatPage.propTypes = propTypes; | ||||||
| NewChatPage.defaultProps = defaultProps; | ||||||
| NewChatPage.displayName = 'NewChatPage'; | ||||||
|
|
||||||
| export default compose( | ||||||
| withLocalize, | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.
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.
using function based on the discussion here