Convert SearchPage to functional component#23076
Conversation
|
CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅ |
|
I have read the CLA Document and I hereby sign the CLA |
| userToInvite, | ||
| }; | ||
| } | ||
| const debouncedUpdateOptions =_.debounce(updateOptions, 75); |
There was a problem hiding this comment.
_.debounce returns always a new function, probably would be good to wrap it into an useCallback for the useEffect, so you can add it into the dependency array safely
There was a problem hiding this comment.
Yes, the app should have the same reference across the re-renders. useCallback is necessary here.
| this.setState({searchValue}, this.debouncedUpdateOptions); | ||
| } | ||
| useEffect(() => { | ||
| updateOptions(); |
There was a problem hiding this comment.
if updateOptions isn't part of the dependency array, please add a comment explaining why
There was a problem hiding this comment.
@gedu, wdyt about the next option - https://github.com/Expensify/App/pull/23076/files#r1267798688?
There was a problem hiding this comment.
I just realized that updateOptions useCallback, also is recreated when the searchValue change, I think could be some useEffects and function duplications, would be good to update the logic, and merge where is possible
| personalDetails, | ||
| }); | ||
| useEffect(() => { | ||
| debouncedUpdateOptions() |
There was a problem hiding this comment.
why are you updating the options after every new word? You are listening to reports and personalDetails to update the options
| SearchPage.propTypes = propTypes; | ||
| SearchPage.defaultProps = defaultProps; | ||
|
|
||
| SearchPage.displayName = 'SearchPage'; |
There was a problem hiding this comment.
use hooks useLocalize and useWindowDimensions
| } | ||
| useEffect(() => { | ||
| updateOptions(); | ||
| },[props.reports, props.personalDetails]) |
There was a problem hiding this comment.
I think this useEffect should have only this function as a dependency. It is logically. Finally, it uses the same deps.
| },[props.reports, props.personalDetails]) | |
| },[updateOptions]) |
| recentReports, | ||
| personalDetails, | ||
| }); | ||
| useEffect(() => { |
There was a problem hiding this comment.
Could you please group all useEffects in one place instead of having them in random places across the component.
| class SearchPage extends Component { | ||
| constructor(props) { | ||
| super(props); | ||
| function SearchPage(props) { |
There was a problem hiding this comment.
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.
Also, you have to remove usage of defaultProps 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.
fabioh8010
left a comment
There was a problem hiding this comment.
Just one nitpick, LGTM!
| reports: {}, | ||
| }; | ||
| function SearchPage({betas = [], personalDetails = {}, reports= {}}) { | ||
| //Data for initialization (runs only on the first render) |
There was a problem hiding this comment.
| //Data for initialization (runs only on the first render) | |
| // Data for initialization (runs only on the first render) |
| personalDetails: {}, | ||
| reports: {}, | ||
| }; | ||
| function SearchPage({betas = [], personalDetails = {}, reports= {}}) { |
There was a problem hiding this comment.
| function SearchPage({betas = [], personalDetails = {}, reports= {}}) { | |
| function SearchPage({betas = [], personalDetails = {}, reports = {}}) { |
Super nitpick sorry XD
|
@Piotrfj The whole searching flow looks more complex than it needs to be, in my opinion. I know it was already like this before, but I propose the following to make it simpler and prevent a few re-renders:
This is how it would look like in the end: function SearchPage({betas = [], personalDetails = {}, reports= {}}) {
...
const [searchValue, setSearchValue] = useState('')
// Only 1 state for the 3 objects here
const [searchOptions, setSearchOptions] = useState({
activeRecentReports: initialRecentReports,
activePersonalDetails: initialPersonalDetails,
activeUserToInvite: initialUserToInvite
});
const {translate} = useLocalize();
// delete updateOptions
// delete debouncedUpdateOptions
...
// delete the debouncedUpdateOptions useEffect
...
const onChangeText = (value = '') => {
setSearchValue(value);
// `updateOptions` logic is here, with the debounce
_.debounce(() => {
const {recentReports: localRecentReports, personalDetails: localPersonalDetails, userToInvite: localUserToInvite} = OptionsListUtils.getSearchOptions(
reports,
personalDetails,
searchValue.trim(),
betas,
);
// only 1 state change here prevents rerenders
setSearchOptions({
activeUserToInvite: localUserToInvite,
activeRecentReports: localRecentReports,
activePersonalDetails: localPersonalDetails,
});
}, 75);
}
}This way when you look at |
| } | ||
| useEffect(() => { | ||
| debouncedUpdateOptions() | ||
| }, [searchValue, debouncedUpdateOptions]) |
There was a problem hiding this comment.
Actually, searchValue not necessary here.
useEffect has debouncedUpdateOptions has updateOptions has searchValue. BTW, if you want to simplify this chain, you can pass searchValue as an argument to updateOptions.
| }, [searchValue, debouncedUpdateOptions]) | |
| }, [debouncedUpdateOptions]) |
| }; | ||
| function SearchPage({betas = [], personalDetails = {}, reports = {}}) { | ||
| // Data for initialization (runs only on the first render) | ||
| const {recentReports: initialRecentReports, personalDetails: initialPersonalDetails, userToInvite: initialUserToInvite} = useMemo(() => OptionsListUtils.getSearchOptions(reports, personalDetails, '', betas), []); |
There was a problem hiding this comment.
Linter will complain on this, so just disable this rule
| const {recentReports: initialRecentReports, personalDetails: initialPersonalDetails, userToInvite: initialUserToInvite} = useMemo(() => OptionsListUtils.getSearchOptions(reports, personalDetails, '', betas), []); | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| const {recentReports: initialRecentReports, personalDetails: initialPersonalDetails, userToInvite: initialUserToInvite} = useMemo(() => OptionsListUtils.getSearchOptions(reports, personalDetails, '', betas), []); |
|
@thesahindia Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
|
@thesahindia let's get this tested and reviewed 🙏 🙇 |
@Piotrfj Can you also please resolve the outstanding comments on your PR? |
|
@marcaaron @allroundexperts please accept a pr |
|
@Piotrfj I'm on it today! |
|
@Piotrfj I still see some un-resolved comments.
Can you please resolve these? |
|
@allroundexperts @Piotrfj You can ignore this comment, is not valid:
But @Piotrfj let's do this one:
|
|
@Piotrfj Can you please upload screen recordings for all the platforms? |
|
@allroundexperts sure, gonna provide them today |
|
Bump @Piotrfj |
|
@allroundexperts examples provided, sorry for the delay, I was actually providing them earlier but forgot to save them... |
marcaaron
left a comment
There was a problem hiding this comment.
Nice changes overall. We are on a merge freeze I think for the next couple of days. So, we will wait a bit before merging. If it goes for too long please tag me back in here!
|
The checklist also looks like it went out of date somewhere if we can fix that in the meantime @allroundexperts |
|
Merge freeze is over so taking this off hold. @allroundexperts can you check my comment above? |
Hi @marcaaron! |
allroundexperts
left a comment
There was a problem hiding this comment.
Still testing well!
|
This is ready @marcaaron! |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚀 Deployed to staging by https://github.com/marcaaron in version: 1.3.70-0 🚀
|
|
🚀 Deployed to production by https://github.com/thienlnam in version: 1.3.70-8 🚀
|

Details
Fixed Issues
$ #16251
PROPOSAL: proposal not yet created
Tests
Offline tests
QA Steps
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)myBool && <MyComponent />.src/languages/*files and using the translation methodWaiting for Copylabel for a copy review on the original GH to get the correct copy.STYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)/** comment above it */thisproperly so there are no scoping issues (i.e. foronClick={this.submit}the methodthis.submitshould be bound tothisin the constructor)thisare necessary to be bound (i.e. avoidthis.submit = this.submit.bind(this);ifthis.submitis never passed to a component event handler likeonClick)StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG))Avataris modified, I verified thatAvataris working as expected in all cases)ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Web
Kapture.2023-08-23.at.14.57.44.mp4
Mobile Web - Chrome
Mobile Web - Safari
Desktop
Kapture.2023-08-23.at.14.57.44.mp4
iOS
Kapture.2023-08-24.at.13.12.01.mp4
Android
Kapture.2023-08-23.at.15.14.34.mp4