-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Migrate MoneyRequestParticipantsSplitSelector.js to function component #20271
Changes from 8 commits
f8661e7
8ecd4fb
0b66de4
e10cd88
35b3f79
4c375f9
041d782
c068bfb
4dd5f0a
ffd6fad
49252c1
82be71e
a1e5ba1
05a9937
0afb3e6
b70b37e
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,4 +1,4 @@ | ||
import React, {Component} from 'react'; | ||
import React, {useEffect, useState} from 'react'; | ||
import {View} from 'react-native'; | ||
import PropTypes from 'prop-types'; | ||
import _ from 'underscore'; | ||
|
@@ -60,183 +60,157 @@ const defaultProps = { | |
safeAreaPaddingBottomStyle: {}, | ||
}; | ||
|
||
class MoneyRequestParticipantsSplitSelector extends Component { | ||
constructor(props) { | ||
super(props); | ||
|
||
this.toggleOption = this.toggleOption.bind(this); | ||
this.finalizeParticipants = this.finalizeParticipants.bind(this); | ||
this.updateOptionsWithSearchTerm = this.updateOptionsWithSearchTerm.bind(this); | ||
|
||
const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( | ||
props.reports, | ||
props.personalDetails, | ||
props.betas, | ||
'', | ||
props.participants, | ||
CONST.EXPENSIFY_EMAILS, | ||
); | ||
|
||
this.state = { | ||
searchTerm: '', | ||
recentReports, | ||
personalDetails, | ||
userToInvite, | ||
}; | ||
} | ||
|
||
componentDidUpdate(prevProps) { | ||
if (_.isEqual(prevProps.reports, this.props.reports) && _.isEqual(prevProps.personalDetails, this.props.personalDetails)) { | ||
return; | ||
} | ||
this.updateOptionsWithSearchTerm(this.state.searchTerm); | ||
} | ||
function MoneyRequestParticipantsSplitSelector(props) { | ||
const [searchTerm, setSearchTerm] = useState(''); | ||
const [newChatOptions, setNewChatOptions] = useState({ | ||
recentReports: [], | ||
personalDetails: [], | ||
userToInvite: null, | ||
}); | ||
|
||
/** | ||
* Returns the sections needed for the OptionsSelector | ||
* | ||
* @param {Boolean} maxParticipantsReached | ||
* @returns {Array} | ||
*/ | ||
getSections(maxParticipantsReached) { | ||
const sections = []; | ||
const getSections = (maxParticipantsReached) => { | ||
const newSections = []; | ||
let indexOffset = 0; | ||
|
||
sections.push({ | ||
newSections.push({ | ||
title: undefined, | ||
data: this.props.participants, | ||
data: props.participants, | ||
shouldShow: true, | ||
indexOffset, | ||
}); | ||
indexOffset += this.props.participants.length; | ||
indexOffset += props.participants.length; | ||
|
||
if (maxParticipantsReached) { | ||
return sections; | ||
return newSections; | ||
} | ||
|
||
sections.push({ | ||
title: this.props.translate('common.recents'), | ||
data: this.state.recentReports, | ||
shouldShow: !_.isEmpty(this.state.recentReports), | ||
const {recentReports, personalDetails, userToInvite} = newChatOptions; | ||
|
||
newSections.push({ | ||
title: props.translate('common.recents'), | ||
data: recentReports, | ||
shouldShow: !_.isEmpty(recentReports), | ||
indexOffset, | ||
}); | ||
indexOffset += this.state.recentReports.length; | ||
indexOffset += recentReports.length; | ||
|
||
sections.push({ | ||
title: this.props.translate('common.contacts'), | ||
data: this.state.personalDetails, | ||
shouldShow: !_.isEmpty(this.state.personalDetails), | ||
newSections.push({ | ||
title: props.translate('common.contacts'), | ||
data: personalDetails, | ||
shouldShow: !_.isEmpty(personalDetails), | ||
indexOffset, | ||
}); | ||
indexOffset += this.state.personalDetails.length; | ||
indexOffset += personalDetails.length; | ||
|
||
if (this.state.userToInvite && !OptionsListUtils.isCurrentUser(this.state.userToInvite)) { | ||
sections.push({ | ||
if (userToInvite && !OptionsListUtils.isCurrentUser(userToInvite)) { | ||
newSections.push({ | ||
undefined, | ||
data: [this.state.userToInvite], | ||
data: [userToInvite], | ||
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.participants, | ||
CONST.EXPENSIFY_EMAILS, | ||
); | ||
this.setState({ | ||
searchTerm, | ||
userToInvite, | ||
recentReports, | ||
personalDetails, | ||
}); | ||
} | ||
|
||
/** | ||
* Once a single or more users are selected, navigates to next step | ||
*/ | ||
finalizeParticipants() { | ||
this.props.onStepComplete(); | ||
} | ||
return newSections; | ||
}; | ||
|
||
/** | ||
* Removes a selected option from list if already selected. If not already selected add this option to the list. | ||
* @param {Object} option | ||
*/ | ||
toggleOption(option) { | ||
const isOptionInList = _.some(this.props.participants, (selectedOption) => selectedOption.accountID === option.accountID); | ||
const toggleOption = (option) => { | ||
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 we need to wrap this inside 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. Wrapped inside useCallback. |
||
const isOptionInList = _.some(props.participants, (selectedOption) => selectedOption.accountID === option.accountID); | ||
|
||
let newSelectedOptions; | ||
|
||
if (isOptionInList) { | ||
newSelectedOptions = _.reject(this.props.participants, (selectedOption) => selectedOption.accountID === option.accountID); | ||
newSelectedOptions = _.reject(props.participants, (selectedOption) => selectedOption.accountID === option.accountID); | ||
} else { | ||
newSelectedOptions = [...this.props.participants, option]; | ||
newSelectedOptions = [...props.participants, option]; | ||
} | ||
|
||
this.props.onAddParticipants(newSelectedOptions); | ||
|
||
this.setState((prevState) => { | ||
const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( | ||
this.props.reports, | ||
this.props.personalDetails, | ||
this.props.betas, | ||
isOptionInList ? prevState.searchTerm : '', | ||
newSelectedOptions, | ||
CONST.EXPENSIFY_EMAILS, | ||
); | ||
return { | ||
recentReports, | ||
personalDetails, | ||
userToInvite, | ||
searchTerm: isOptionInList ? prevState.searchTerm : '', | ||
}; | ||
}); | ||
} | ||
|
||
render() { | ||
const maxParticipantsReached = this.props.participants.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 {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( | ||
props.reports, | ||
props.personalDetails, | ||
props.betas, | ||
isOptionInList ? searchTerm : '', | ||
newSelectedOptions, | ||
CONST.EXPENSIFY_EMAILS, | ||
); | ||
const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(this.props.personalDetails); | ||
|
||
return ( | ||
<View style={[styles.flex1, styles.w100, this.props.participants.length > 0 ? this.props.safeAreaPaddingBottomStyle : {}]}> | ||
<OptionsSelector | ||
canSelectMultipleOptions | ||
sections={sections} | ||
selectedOptions={this.props.participants} | ||
value={this.state.searchTerm} | ||
onSelectRow={this.toggleOption} | ||
onChangeText={this.updateOptionsWithSearchTerm} | ||
headerMessage={headerMessage} | ||
boldStyle | ||
shouldShowConfirmButton | ||
confirmButtonText={this.props.translate('common.next')} | ||
onConfirmSelection={this.finalizeParticipants} | ||
textInputLabel={this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} | ||
safeAreaPaddingBottomStyle={this.props.safeAreaPaddingBottomStyle} | ||
shouldShowOptions={isOptionsDataReady} | ||
/> | ||
</View> | ||
|
||
setNewChatOptions({ | ||
recentReports, | ||
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. Don't you need to set 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. No, we didn't. 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. But in the original code, we're resetting the search term if option is in the list. Why were we doing that then? 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. Yeah, you're right. |
||
userToInvite, | ||
}); | ||
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. Please use this inside the callback like it was being done previously. 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. @allroundexperts |
||
props.onAddParticipants(newSelectedOptions); | ||
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 changing the order here? 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. Hi, @allroundexperts 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. It's very difficult to compare the before and after code with the order changed. Can you please change the order as it was previously? Thanks! 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. Hi, @allroundexperts |
||
}; | ||
|
||
/** | ||
* Once a single or more users are selected, navigates to next step | ||
*/ | ||
const finalizeParticipants = () => { | ||
props.onStepComplete(); | ||
}; | ||
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. Again, why is the order being changed here? 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. Hi, @allroundexperts 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. Just for my understanding, why don't we use 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 we don't need to even wrap this inside the function since the function does nothing other than calling the function supplied in the prop. @multijump can you please remove this and use the prop directly? 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. Hi, @allroundexperts |
||
|
||
const maxParticipantsReached = props.participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; | ||
const headerMessage = OptionsListUtils.getHeaderMessage( | ||
newChatOptions.personalDetails.length + newChatOptions.recentReports.length !== 0, | ||
Boolean(newChatOptions.userToInvite), | ||
searchTerm, | ||
maxParticipantsReached, | ||
); | ||
const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(props.personalDetails); | ||
|
||
const sections = getSections(maxParticipantsReached); | ||
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 is this needed? 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. Hi, @allroundexperts |
||
|
||
useEffect(() => { | ||
const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getNewChatOptions( | ||
props.reports, | ||
props.personalDetails, | ||
props.betas, | ||
searchTerm, | ||
props.participants, | ||
CONST.EXPENSIFY_EMAILS, | ||
); | ||
} | ||
setNewChatOptions({ | ||
recentReports, | ||
personalDetails, | ||
userToInvite, | ||
}); | ||
}, [props.betas, props.reports, props.participants, props.personalDetails, props.translate, searchTerm]); | ||
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 don't see the original implementation having such thing. Can you please explain why this is needed? 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. You can see the 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. Which line exactly are you referring to @allroundexperts? If it's 181 that makes sense to me as we'll trigger updates based on those changes. But let me know if you mean somethign else. 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 was talking about the whole 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.
These are original implementation. We can change these functions using
Like above. And in the original component, whenever change Actually, in the original implementation, main functional block was FYI, I thought it is not good to reuse the callback function in the component. |
||
|
||
return ( | ||
<View style={[styles.flex1, styles.w100, props.participants.length > 0 ? props.safeAreaPaddingBottomStyle : {}]}> | ||
<OptionsSelector | ||
canSelectMultipleOptions | ||
sections={sections} | ||
selectedOptions={props.participants} | ||
value={searchTerm} | ||
onSelectRow={toggleOption} | ||
onChangeText={setSearchTerm} | ||
headerMessage={headerMessage} | ||
boldStyle | ||
shouldShowConfirmButton | ||
confirmButtonText={props.translate('common.next')} | ||
onConfirmSelection={finalizeParticipants} | ||
textInputLabel={props.translate('optionsSelector.nameEmailOrPhoneNumber')} | ||
safeAreaPaddingBottomStyle={props.safeAreaPaddingBottomStyle} | ||
shouldShowOptions={isOptionsDataReady} | ||
/> | ||
</View> | ||
); | ||
} | ||
|
||
MoneyRequestParticipantsSplitSelector.propTypes = propTypes; | ||
MoneyRequestParticipantsSplitSelector.defaultProps = defaultProps; | ||
MoneyRequestParticipantsSplitSelector.displayName = 'MoneyRequestParticipantsSplitSelector'; | ||
|
||
export default compose( | ||
withLocalize, | ||
|
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.
We need to wrap this inside
useCallback
.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.
getSections
is not callback function so I think it is not good to use the useCallback.How about to use the useMemo?