Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1ea1f7a
import hook methods and create new state/ref variables
bondydaa Apr 14, 2023
2cd76d1
port methods to new format
bondydaa Apr 14, 2023
a7768f0
update remaining component body to work with functional component
bondydaa Apr 14, 2023
64c0f0f
i think fix error related to ref being an object not a function
bondydaa Apr 14, 2023
f14b76c
Merge branch 'main' of github.com:Expensify/App into bondy-login-comp…
bondydaa Apr 14, 2023
b55028d
undo because it breaks everything
bondydaa Apr 14, 2023
c79c808
remove "this" left over from class implementation
bondydaa Apr 17, 2023
da65870
use different name b/c password is already defined
bondydaa Apr 17, 2023
615c441
no need for arrow functions can just call the state set methods directly
bondydaa Apr 17, 2023
ce4ba20
convert from method to boolean value and add useMemo to prevent perfo…
bondydaa Apr 17, 2023
80b2d92
support both hook style refs and class style hooks
bondydaa Apr 17, 2023
d058367
use generic object type instead of custom shape
bondydaa Apr 18, 2023
50cb6c6
fix import style
bondydaa Apr 18, 2023
c109533
Merge branch 'main' of github.com:Expensify/App into bondy-login-comp…
bondydaa Apr 18, 2023
f52c650
Merge branch 'main' of github.com:Expensify/App into bondy-login-comp…
bondydaa Apr 18, 2023
67cad62
fix conflict, ensure proptype for inner ref is gerneric as possible u…
bondydaa Apr 20, 2023
5bcd178
fixing conflicts
bondydaa Apr 25, 2023
8adab08
create handlers so we can always trim the values before adding them t…
bondydaa Apr 25, 2023
3380130
remove unnecessary dependency
bondydaa Apr 25, 2023
a061347
cant use useCallback since we dont have a dependency to pass it
bondydaa Apr 25, 2023
91e49c9
use useCallback with empty dependency array to avoid performance woes
bondydaa Apr 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/components/TextInput/baseTextInputPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ const propTypes = {
/** Forward the inner ref */
innerRef: PropTypes.oneOfType([

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took this from here #17472 (comment) thanks @eVoloshchak !

And it got rid of the console warning I was getting. I tested that it didn't break the sign-in page either which happened the last time I tried to solve this so think it's working 🙏

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On IOS I get this error :

Screenshot 2023-04-17 at 11 42 51 PM

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this should be fixed by the change to Proptypes.object, let me see if I can get ios working locally.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has this been resolved?

PropTypes.func,
// eslint-disable-next-line react/forbid-prop-types
PropTypes.shape({current: PropTypes.any}),
PropTypes.object,
]),

/** Maximum characters allowed */
Expand Down
163 changes: 75 additions & 88 deletions src/pages/settings/Profile/Contacts/NewContactMethodPage.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React, {Component} from 'react';
import React, {
useCallback, useMemo, useRef, useState,
} from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import {ScrollView} from 'react-native-gesture-handler';
Expand Down Expand Up @@ -52,102 +54,87 @@ const defaultProps = {
loginList: {},
};

class NewContactMethodPage extends Component {
constructor(props) {
super(props);

this.state = {
login: '',
password: '',
};
this.onLoginChange = this.onLoginChange.bind(this);
this.validateForm = this.validateForm.bind(this);
this.submitForm = this.submitForm.bind(this);
}

onLoginChange(login) {
this.setState({login});
}

/**
* Determine whether the form is valid
*
* @returns {Boolean}
*/
validateForm() {
const login = this.state.login.trim();
function NewContactMethodPage(props) {

Copy link
Copy Markdown
Contributor

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

Suggested change
function NewContactMethodPage(props) {
const NewContactMethodPage = (props) => {

Copy link
Copy Markdown
Contributor Author

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.

const [login, setLogin] = useState('');
const [password, setPassword] = useState('');
const loginInputRef = useRef(null);

const handleLoginChange = useCallback((value) => {
setLogin(value.trim());
}, []);

const handlePasswordChange = useCallback((value) => {
setPassword(value.trim());
}, []);

const isFormValid = useMemo(() => {
const phoneLogin = LoginUtils.getPhoneNumberWithoutSpecialChars(login);

return (Permissions.canUsePasswordlessLogins(this.props.betas) || this.state.password)
return (Permissions.canUsePasswordlessLogins(props.betas) || password)
&& (Str.isValidEmail(login) || Str.isValidPhone(phoneLogin));
}

submitForm() {
// Trim leading and trailing space from login
const login = this.state.login.trim();
}, [login, password, props.betas]);

const submitForm = useCallback(() => {

Copy link
Copy Markdown
Contributor

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?

Copy link
Copy Markdown
Contributor Author

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, thanks

Copy link
Copy Markdown
Contributor Author

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.

// If this login already exists, just go back.
if (lodashGet(this.props.loginList, login)) {
if (lodashGet(props.loginList, login)) {
Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
return;
}
User.addNewContactMethodAndNavigate(login, this.state.password);
}

render() {
return (
<ScreenWrapper
onTransitionEnd={() => {
if (!this.loginInputRef) {
return;
}
this.loginInputRef.focus();
}}
>
<HeaderWithCloseButton
title={this.props.translate('contacts.newContactMethod')}
shouldShowBackButton
onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS)}
onCloseButtonPress={() => Navigation.dismissModal(true)}
/>
<ScrollView>
<Text style={[styles.ph5, styles.mb5]}>
{this.props.translate('common.pleaseEnterEmailOrPhoneNumber')}
</Text>
<View style={[styles.ph5, styles.mb6]}>
<TextInput
label={`${this.props.translate('common.email')}/${this.props.translate('common.phoneNumber')}`}
ref={el => this.loginInputRef = el}
value={this.state.login}
onChangeText={this.onLoginChange}
autoCapitalize="none"
returnKeyType={Permissions.canUsePasswordlessLogins(this.props.betas) ? 'done' : 'next'}
/>
</View>
{!Permissions.canUsePasswordlessLogins(this.props.betas)
&& (
<View style={[styles.ph5, styles.mb6]}>
<TextInput
label={this.props.translate('common.password')}
value={this.state.password}
onChangeText={password => this.setState({password})}
returnKeyType="done"
/>
</View>
)}
</ScrollView>
<FixedFooter style={[styles.flexGrow0]}>
<Button
success
isDisabled={!this.validateForm()}
text={this.props.translate('common.add')}
onPress={this.submitForm}
pressOnEnter
User.addNewContactMethodAndNavigate(login, password);
}, [login, props.loginList, password]);

return (
<ScreenWrapper
onTransitionEnd={() => {
if (!loginInputRef.current) {
return;
}
loginInputRef.current.focus();
}}
>
<HeaderWithCloseButton
title={props.translate('contacts.newContactMethod')}
shouldShowBackButton
onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS)}
onCloseButtonPress={() => Navigation.dismissModal(true)}
/>
<ScrollView>
<Text style={[styles.ph5, styles.mb5]}>
{props.translate('common.pleaseEnterEmailOrPhoneNumber')}
</Text>
<View style={[styles.ph5, styles.mb6]}>
<TextInput
label={`${props.translate('common.email')}/${props.translate('common.phoneNumber')}`}
ref={loginInputRef}
value={login}
onChangeText={handleLoginChange}
autoCapitalize="none"
returnKeyType={Permissions.canUsePasswordlessLogins(props.betas) ? 'done' : 'next'}
/>
</FixedFooter>
</ScreenWrapper>
);
}
</View>
{!Permissions.canUsePasswordlessLogins(props.betas)
&& (
<View style={[styles.ph5, styles.mb6]}>
<TextInput
label={props.translate('common.password')}
value={password}
onChangeText={handlePasswordChange}
returnKeyType="done"
/>
</View>
)}
</ScrollView>
<FixedFooter style={[styles.flexGrow0]}>
<Button
success
isDisabled={!isFormValid}
text={props.translate('common.add')}
onPress={submitForm}
pressOnEnter
/>
</FixedFooter>
</ScreenWrapper>
);
}

NewContactMethodPage.propTypes = propTypes;
Expand Down