-
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
Refactor NewContactMethodPage to use hooks #17472
Conversation
@eVoloshchak @Beamanator One of you needs to 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] |
I couldn't get ios or android to build locally, probably b/c i was doing old dot dev recently so if the c+ could get those screenshots for me that'd be awesome. otherwise I'll take a look next week. Another thing is that I am seeing this console error Which I tried to fix with this: 64c0f0f but that led to a bunch of other errors in the app, like on the login form so I think we might need to ignore it until we refactor that in a single go. But let me know if there's something else I should try/do. |
Attaching the screen recordings for iOS and Android iOSScreen.Recording.2023-04-17.at.11.44.57.movAndroidScreen_Recording_20230417-114306_New.Expensify.mp4As for the Proptypes error with innerRef, we could change this line to innerRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({current: PropTypes.instanceOf(Element)}),
]), |
|
||
submitForm() { | ||
const submitForm = 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.
Why do we need a useCallback
here?
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.
It probably isn't strictly required here per the react docs https://react.dev/reference/react/useCallback#usage it suggests you probably only need it for a performance optimization.
But I chatted with @marcaaron a bit 1:1 about it, sounds like there was a discussion somewhere (not sure where probably open-source but I haven't looked for it yet) where we landed on preferring to just always use useCallback
the resolution was that we should just always use it since the performance downsides of missing when we should use it vs not can be pretty bad.
Or said another way - we'll probably mess stuff up more by not using it then we will by using it
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.
That makes sense, thanks
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.
hmm interesting thread that seems to be saying the opposite of what we decided here https://expensify.slack.com/archives/C01GTK53T8Q/p1680096510744619
just wanted to share so others didn't keep thinking we should prefer useCallback
by default.
Co-authored-by: Eugene Voloshchak <copyreading@gmail.com>
Co-authored-by: Eugene Voloshchak <copyreading@gmail.com>
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.
Looks super great - nailed pretty much everything 👍👍
validateForm() { | ||
const login = this.state.login.trim(); | ||
const phoneLogin = LoginUtils.getPhoneNumberWithoutSpecialChars(login); | ||
const validateForm = 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.
NAB, since we are calling this validateForm()
method in the render... I'd maybe update this to be a useMemo()
and return the value instead of a function...
const isFormValid = useMemo(() => {...}, dependencies);
It's kind of minor, but I think slightly better in that we don't need to call the function on render and can just use the value directly instead.
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.
yeah i was really confused looking at this and was thinking it'd be better if it was a single value not a function but then didn't want to accidentally break something. can update though.
I def don't know enough to feel comfortable signing off on this change #17472 (comment) @marcaaron what are your thoughts? Would that allow us to use both the current code that exists everywhere and then use the |
I think the suggestion is fine. Ultimately, it's not guaranteed that all refs would have the |
label={`${props.translate('common.email')}/${props.translate('common.phoneNumber')}`} | ||
ref={loginInputRef} | ||
value={login} | ||
onChangeText={value => setLogin(value)} |
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.
onChangeText={value => setLogin(value)} | |
onChangeText={setLogin} |
Instead of defining a new arrow function, I believe we can simply pass setLogin
here. This would result in a minor performance boost, as we would not need to recreate the function each time the component is updated.
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.
oh cool thanks, makes sense. I don't think I realized the value would be passed by default to the function here.
<TextInput | ||
label={props.translate('common.password')} | ||
value={password} | ||
onChangeText={value => setPassword(value)} |
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.
onChangeText={value => setPassword(value)} | |
onChangeText={setPassword} |
Same here.
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.
Tests well
@marcaaron @Beamanator @cristipaval can you give it another look please before more conflicts pop up 🙏 |
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.
Looks great! 1 tiny question, approving since it's very small
@@ -47,7 +47,10 @@ const propTypes = { | |||
hideFocusedState: PropTypes.bool, | |||
|
|||
/** Forward the inner ref */ | |||
innerRef: PropTypes.func, | |||
innerRef: PropTypes.oneOfType([ |
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.
Has this been resolved?
class NewContactMethodPage extends Component { | ||
constructor(props) { | ||
super(props); | ||
function NewContactMethodPage(props) { |
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.
Suuuper just wondering, why aren't we doing this? I see some components doing this & some doing what you have, I feel like it would be nice to standardize but maybe it doesn't matter for now
function NewContactMethodPage(props) { | |
const NewContactMethodPage = (props) => { |
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.
The difference between these 2 things is that defining it as a function means that it will be hoisted into the global scope. Defining it as a variable prevents this hoisting. See for the nitty gritty on why is may or may not matter https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/scope-closures/ch5.md#hoisting-declaration-vs-expression
But I agree, right now we are fine with either and we should probably standardize on one or the other. I don't have a strong preference for either format.
:marshmellow-duet: conflicts again, fixing them. |
8adab08
const handleLoginChange = useCallback((value) => { | ||
setLogin(value.trim()); | ||
}, [login]); | ||
|
||
const handlePasswordChange = useCallback((value) => { | ||
setPassword(value.trim()); | ||
}, [password]); |
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.
I did this to fix the merge conflict caused by #17817 and think it's a better solution so that the values in state
never have leading or trailing whitespace.
It does add one small UX quirk that I'm not sure if we care about: https://user-images.githubusercontent.com/4073354/234353895-d83bf242-44b4-48fc-8032-f85a0323c50b.mp4
basically if you go back to the start of the text and hit a space
b/c we .trim
the value the cursor jumps to the end of the text.
Personally I don't really think this is a problem and probably only something QA will do while testing - real users most likely are not attempting this and will require a bit more code to prevent it but open to other opinions.
updated |
const handleLoginChange = value => setLogin(value.trim()); | ||
const handlePasswordChange = value => setPassword(value.trim()); |
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.
@bondydaa We can use useCallback
and we pass empty dependency array
const handlePasswordChange = useCallback((value) => {
setPassword(value.trim());
}, []);
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.
ah that is probably what I was missing! thanks for the tip.
(dj khaled voice) and another 1. |
✋ 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.6-0 🚀
|
🚀 Deployed to production by https://github.com/jasperhuangg in version: 1.3.6-0 🚀
|
Details
Refactoring the
NewContactMethodPage
component to a functional component and to use hooks.Fixed Issues
$ #16291
Tests And QA Steps
Offline tests
PR Author Checklist
### Fixed Issues
section aboveTests
sectionOffline steps
sectionQA steps
sectiontoggleReport
and notonIconClick
)src/languages/*
files and using the translation methodWaiting for Copy
label for a copy review on the original GH to get the correct copy.STYLE.md
) were followedAvatar
, I verified the components usingAvatar
are working as expected)/** comment above it */
this
properly so there are no scoping issues (i.e. foronClick={this.submit}
the methodthis.submit
should be bound tothis
in the constructor)this
are necessary to be bound (i.e. avoidthis.submit = this.submit.bind(this);
ifthis.submit
is never passed to a component event handler likeonClick
)StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)
)Avatar
is modified, I verified thatAvatar
is working as expected in all cases)ScrollView
component to make it scrollable when more elements are added to the page.main
branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTest
steps.Screenshots/Videos
Web
16291_web.mp4
Mobile Web - Chrome
Mobile Web - Safari
Desktop
2023-04-14_17-10-11.mp4
iOS
Android