From 09cf7dc362d78781d2efd734018780caa634a126 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 7 Jun 2023 11:39:51 -0400 Subject: [PATCH 1/7] Initial coarse refactor, added usePrevious hook, removed lodash dependency --- src/hooks/usePrevious.js | 15 + .../ValidateCodeForm/BaseValidateCodeForm.js | 284 +++++++++--------- 2 files changed, 154 insertions(+), 145 deletions(-) create mode 100644 src/hooks/usePrevious.js diff --git a/src/hooks/usePrevious.js b/src/hooks/usePrevious.js new file mode 100644 index 000000000000..1059df00c8de --- /dev/null +++ b/src/hooks/usePrevious.js @@ -0,0 +1,15 @@ +import {useEffect, useRef} from 'react'; + +/** + * A hook that returns the previous value of a variable + * + * @param {*} value + * @returns {*} + */ +export default function usePrevious(value) { + const ref = useRef(); + useEffect(() => { + ref.current = value; + }, [value]); + return ref.current; +} \ No newline at end of file diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js index 7069d2602430..3a509d9618d3 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js @@ -1,9 +1,7 @@ -import React from 'react'; +import React, { useState } from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import lodashGet from 'lodash/get'; import styles from '../../../styles/styles'; import Button from '../../../components/Button'; import Text from '../../../components/Text'; @@ -25,6 +23,7 @@ import FormHelpMessage from '../../../components/FormHelpMessage'; import MagicCodeInput from '../../../components/MagicCodeInput'; import Terms from '../Terms'; import PressableWithFeedback from '../../../components/Pressable/PressableWithFeedback'; +import usePrevious from '../../../hooks/usePrevious'; const propTypes = { /* Onyx Props */ @@ -63,47 +62,47 @@ const defaultProps = { preferredLocale: CONST.LOCALES.DEFAULT, }; -class BaseValidateCodeForm extends React.Component { - constructor(props) { - super(props); - this.validateAndSubmitForm = this.validateAndSubmitForm.bind(this); - this.resendValidateCode = this.resendValidateCode.bind(this); - this.clearSignInData = this.clearSignInData.bind(this); +function BaseValidateCodeForm(props) { + const [formError, setFormError] = useState({}); + const [validateCode, setValidateCode] = useState(props.credentials.validateCode || ''); + const [twoFactorAuthCode, setTwoFactorAuthCode] = useState(''); + const [linkSent, setLinkSent] = useState(false); - this.state = { - formError: {}, - validateCode: props.credentials.validateCode || '', - twoFactorAuthCode: '', - linkSent: false, - }; - } + const wasVisible = usePrevious(props.isVisible); + const required2FA = usePrevious(props.account.requiresTwoFactorAuth); + const hadValidateCode = usePrevious(props.credentials.validateAndSubmitForm); + + const refValidateCode = useRef(null); + const inputValidateCode = refValidateCode.current; + const ref2FA = useRef(null); + const input2FA = ref2FA.current; - componentDidMount() { - if (!canFocusInputOnScreenFocus() || !this.inputValidateCode || !this.props.isVisible) { + useEffect(() => { + if (!canFocusInputOnScreenFocus() || !inputValidateCode || !props.isVisible) { return; } - this.inputValidateCode.focus(); - } + inputValidateCode.focus(); + }, []); - componentDidUpdate(prevProps) { - if (!prevProps.isVisible && this.props.isVisible) { - this.inputValidateCode.focus(); + useEffect(() => { + if (!wasVisible && props.isVisible) { + inputValidateCode.focus(); } - if (prevProps.isVisible && !this.props.isVisible && this.state.validateCode) { - this.clearValidateCode(); + if (wasVisible && !props.isVisible && validateCode) { + clearValidateCode(); } // Clear the code input if a new magic code was requested - if (this.props.isVisible && this.state.linkSent && this.props.account.message && this.state.validateCode) { - this.clearValidateCode(); + if (props.isVisible && linkSent && props.account.message && validateCode) { + clearValidateCode(); } - if (!prevProps.credentials.validateCode && this.props.credentials.validateCode) { - this.setState({validateCode: this.props.credentials.validateCode}); + if (!hadValidateCode && props.credentials.validateCode) { + setValidateCode(props.credentials.validateCode); } - if (!prevProps.account.requiresTwoFactorAuth && this.props.account.requiresTwoFactorAuth) { - this.input2FA.focus(); + if (!required2FA && props.account.requiresTwoFactorAuth) { + input2FA.focus(); } - } + }, [props.isVisible, wasVisible, props.account, props.credentials, hadValidateCode, required2FA, validateCode, linkSent]); /** * Handle text input and clear formError upon text change @@ -111,14 +110,13 @@ class BaseValidateCodeForm extends React.Component { * @param {String} text * @param {String} key */ - onTextInput(text, key) { - this.setState({ - [key]: text, - formError: {[key]: ''}, - linkSent: false, - }); + const onTextInput = (text, key) => { + const setInput = key === 'validateCode' ? setValidateCode : setTwoFactorAuthCode; + setInput(text); + setFormError((prevError) => ({...prevError, [key]: ''})); + setLinkSent(false); - if (this.props.account.errors) { + if (props.account.errors) { Session.clearAccountMessages(); } } @@ -126,155 +124,151 @@ class BaseValidateCodeForm extends React.Component { /** * Clear Validate Code from the state */ - clearValidateCode() { - this.setState({validateCode: ''}, () => this.inputValidateCode.clear()); + const clearValidateCode = () => { + setValidateCode(''); + inputValidateCode.clear(); } - + /** * Trigger the reset validate code flow and ensure the 2FA input field is reset to avoid it being permanently hidden */ - resendValidateCode() { - if (this.input2FA) { - this.setState({twoFactorAuthCode: ''}, this.input2FA.clear); + const resendValidateCode = () => { + if (input2FA) { + setTwoFactorAuthCode(''); + input2FA.clear(); } - this.setState({formError: {}}); - User.resendValidateCode(this.props.credentials.login, true); + setFormError({}); + User.resendValidateCode(props.credentials.login, true); // Give feedback to the user to let them know the email was sent so they don't spam the button. - this.setState({linkSent: true}); + setLinkSent(true); } /** * Clears local and Onyx sign in states */ - clearSignInData() { - this.setState({twoFactorAuthCode: '', formError: {}}); + const clearSignInData = () => { + setTwoFactorAuthCode(''); + setFormError({}); Session.clearSignInData(); } /** * Check that all the form fields are valid, then trigger the submit callback */ - validateAndSubmitForm() { - const requiresTwoFactorAuth = this.props.account.requiresTwoFactorAuth; - + const validateAndSubmitForm = () => { + const requiresTwoFactorAuth = props.account.requiresTwoFactorAuth; if (requiresTwoFactorAuth) { - if (this.input2FA) { - this.input2FA.blur(); + if (input2FA) { + input2FA.blur(); } - if (!this.state.twoFactorAuthCode.trim()) { - this.setState({formError: {twoFactorAuthCode: 'validateCodeForm.error.pleaseFillTwoFactorAuth'}}); + if (!twoFactorAuthCode.trim()) { + setFormError({twoFactorAuthCode: 'validateCodeForm.error.pleaseFillTwoFactorAuth'}); return; } if (!ValidationUtils.isValidTwoFactorCode(this.state.twoFactorAuthCode)) { - this.setState({formError: {twoFactorAuthCode: 'passwordForm.error.incorrect2fa'}}); + setFormError({twoFactorAuthCode: 'passwordForm.error.incorrect2fa'}); return; } } else { - if (this.inputValidateCode) { - this.inputValidateCode.blur(); + if (inputValidateCode) { + inputValidateCode.blur(); } - - if (!this.state.validateCode.trim()) { - this.setState({formError: {validateCode: 'validateCodeForm.error.pleaseFillMagicCode'}}); + if (!validateCode.trim()) { + setFormError({validateCode: 'validateCodeForm.error.pleaseFillMagicCode'}); return; } - if (!ValidationUtils.isValidValidateCode(this.state.validateCode)) { - this.setState({formError: {validateCode: 'validateCodeForm.error.incorrectMagicCode'}}); + if (!ValidationUtils.isValidValidateCode(validateCode)) { + setFormError({validateCode: 'validateCodeForm.error.incorrectMagicCode'}); return; } } + setFormError({}); - this.setState({ - formError: {}, - }); - - const accountID = lodashGet(this.props, 'credentials.accountID'); + const accountID = props.credentials.accountID; if (accountID) { - Session.signInWithValidateCode(accountID, this.state.validateCode, this.props.preferredLocale, this.state.twoFactorAuthCode); + Session.signInWithValidateCode(accountID, validateCode, props.preferredLocale, twoFactorAuthCode); } else { - Session.signIn('', this.state.validateCode, this.state.twoFactorAuthCode, this.props.preferredLocale); + Session.signIn('', validateCode, twoFactorAuthCode, props.preferredLocale); } } - render() { - const hasError = Boolean(this.props.account) && !_.isEmpty(this.props.account.errors); - return ( - <> - {/* At this point, if we know the account requires 2FA we already successfully authenticated */} - {this.props.account.requiresTwoFactorAuth ? ( - - (this.input2FA = el)} - label={this.props.translate('common.twoFactorCode')} - name="twoFactorAuthCode" - value={this.state.twoFactorAuthCode} - onChangeText={(text) => this.onTextInput(text, 'twoFactorAuthCode')} - onFulfill={this.validateAndSubmitForm} - maxLength={CONST.TFA_CODE_LENGTH} - errorText={this.state.formError.twoFactorAuthCode ? this.props.translate(this.state.formError.twoFactorAuthCode) : ''} - hasError={hasError} - autoFocus - /> - - ) : ( - - (this.inputValidateCode = el)} - label={this.props.translate('common.magicCode')} - name="validateCode" - value={this.state.validateCode} - onChangeText={(text) => this.onTextInput(text, 'validateCode')} - onFulfill={this.validateAndSubmitForm} - errorText={this.state.formError.validateCode ? this.props.translate(this.state.formError.validateCode) : ''} - hasError={hasError} - autoFocus - /> - - {this.state.linkSent ? ( - {this.props.account.message ? this.props.translate(this.props.account.message) : ''} - ) : ( - - {this.props.translate('validateCodeForm.magicCodeNotReceived')} - - )} - - - )} - - {hasError && } - -