diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index ff97c9be24a6..d50fad0bd2f0 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React from 'react'; +import React, {useEffect, useRef, useCallback} from 'react'; import {ActivityIndicator, View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; @@ -10,17 +10,16 @@ import * as BankAccounts from '../libs/actions/BankAccounts'; import ONYXKEYS from '../ONYXKEYS'; import styles from '../styles/styles'; import themeColors from '../styles/themes/default'; -import compose from '../libs/compose'; -import withLocalize, {withLocalizePropTypes} from './withLocalize'; import Picker from './Picker'; import {plaidDataPropTypes} from '../pages/ReimbursementAccount/plaidDataPropTypes'; import Text from './Text'; import getBankIcon from './Icon/BankIcons'; import Icon from './Icon'; import FullPageOfflineBlockingView from './BlockingViews/FullPageOfflineBlockingView'; -import {withNetwork} from './OnyxProvider'; import CONST from '../CONST'; import KeyboardShortcut from '../libs/KeyboardShortcut'; +import useLocalize from '../hooks/useLocalize'; +import useNetwork from '../hooks/useNetwork'; const propTypes = { /** Contains plaid data */ @@ -52,8 +51,6 @@ const propTypes = { /** Are we adding a withdrawal account? */ allowDebit: PropTypes.bool, - - ...withLocalizePropTypes, }; const defaultProps = { @@ -68,172 +65,163 @@ const defaultProps = { bankAccountID: 0, }; -class AddPlaidBankAccount extends React.Component { - constructor(props) { - super(props); - - this.getPlaidLinkToken = this.getPlaidLinkToken.bind(this); - this.subscribedKeyboardShortcuts = []; - } - - componentDidMount() { - this.subscribeToNavigationShortcuts(); - - // If we're coming from Plaid OAuth flow then we need to reuse the existing plaidLinkToken - if (this.isAuthenticatedWithPlaid()) { - return; - } - - BankAccounts.openPlaidBankLogin(this.props.allowDebit, this.props.bankAccountID); - } - - componentDidUpdate(prevProps) { - if (!prevProps.network.isOffline || this.props.network.isOffline || this.isAuthenticatedWithPlaid()) { - return; - } - - // If we are coming back from offline and we haven't authenticated with Plaid yet, we need to re-run our call to kick off Plaid - BankAccounts.openPlaidBankLogin(this.props.allowDebit, this.props.bankAccountID); - } +function AddPlaidBankAccount({plaidData, selectedPlaidAccountID, plaidLinkToken, onExitPlaid, onSelect, text, receivedRedirectURI, plaidLinkOAuthToken, bankAccountID, allowDebit}) { + const subscribedKeyboardShortcuts = useRef([]); + const previousNetworkState = useRef(); - componentWillUnmount() { - this.unsubscribeToNavigationShortcuts(); - } + const {translate} = useLocalize(); + const {isOffline} = useNetwork(); /** * @returns {String} */ - getPlaidLinkToken() { - if (this.props.plaidLinkToken) { - return this.props.plaidLinkToken; + const getPlaidLinkToken = () => { + if (plaidLinkToken) { + return plaidLinkToken; } - if (this.props.receivedRedirectURI && this.props.plaidLinkOAuthToken) { - return this.props.plaidLinkOAuthToken; + if (receivedRedirectURI && plaidLinkOAuthToken) { + return plaidLinkOAuthToken; } - } + }; /** * @returns {Boolean} + * I'm using useCallback so the useEffect which uses this function doesn't run on every render. */ - isAuthenticatedWithPlaid() { - return ( - (this.props.receivedRedirectURI && this.props.plaidLinkOAuthToken) || - !_.isEmpty(lodashGet(this.props.plaidData, 'bankAccounts')) || - !_.isEmpty(lodashGet(this.props.plaidData, 'errors')) - ); - } + const isAuthenticatedWithPlaid = useCallback( + () => (receivedRedirectURI && plaidLinkOAuthToken) || !_.isEmpty(lodashGet(plaidData, 'bankAccounts')) || !_.isEmpty(lodashGet(plaidData, 'errors')), + [plaidData, plaidLinkOAuthToken, receivedRedirectURI], + ); /** * Blocks the keyboard shortcuts that can navigate */ - subscribeToNavigationShortcuts() { + const subscribeToNavigationShortcuts = () => { // find and block the shortcuts const shortcutsToBlock = _.filter(CONST.KEYBOARD_SHORTCUTS, (x) => x.type === CONST.KEYBOARD_SHORTCUTS_TYPES.NAVIGATION_SHORTCUT); - this.subscribedKeyboardShortcuts = _.map(shortcutsToBlock, (shortcut) => + subscribedKeyboardShortcuts.current = _.map(shortcutsToBlock, (shortcut) => KeyboardShortcut.subscribe( shortcut.shortcutKey, () => {}, // do nothing shortcut.descriptionKey, shortcut.modifiers, false, - () => lodashGet(this.props.plaidData, 'bankAccounts', []).length > 0, // start bubbling when there are bank accounts + () => lodashGet(plaidData, 'bankAccounts', []).length > 0, // start bubbling when there are bank accounts ), ); - } + }; /** * Unblocks the keyboard shortcuts that can navigate */ - unsubscribeToNavigationShortcuts() { - _.each(this.subscribedKeyboardShortcuts, (unsubscribe) => unsubscribe()); - this.subscribedKeyboardShortcuts = []; - } + const unsubscribeToNavigationShortcuts = () => { + _.each(subscribedKeyboardShortcuts.current, (unsubscribe) => unsubscribe()); + subscribedKeyboardShortcuts.current = []; + }; - render() { - const plaidBankAccounts = lodashGet(this.props.plaidData, 'bankAccounts') || []; - const token = this.getPlaidLinkToken(); - const options = _.map(plaidBankAccounts, (account) => ({ - value: account.plaidAccountID, - label: `${account.addressName} ${account.mask}`, - })); - const {icon, iconSize} = getBankIcon(); - const plaidErrors = lodashGet(this.props.plaidData, 'errors'); - const plaidDataErrorMessage = !_.isEmpty(plaidErrors) ? _.chain(plaidErrors).values().first().value() : ''; - const bankName = lodashGet(this.props.plaidData, 'bankName'); - - // Plaid Link view - if (!plaidBankAccounts.length) { - return ( - - {lodashGet(this.props.plaidData, 'isLoading') && ( - - - - )} - {Boolean(plaidDataErrorMessage) && {plaidDataErrorMessage}} - {Boolean(token) && !bankName && ( - { - Log.info('[PlaidLink] Success!'); - BankAccounts.openPlaidBankAccountSelector(publicToken, metadata.institution.name, this.props.allowDebit, this.props.bankAccountID); - }} - onError={(error) => { - Log.hmmm('[PlaidLink] Error: ', error.message); - }} - // User prematurely exited the Plaid flow - // eslint-disable-next-line react/jsx-props-no-multi-spaces - onExit={this.props.onExitPlaid} - receivedRedirectURI={this.props.receivedRedirectURI} - /> - )} - - ); + useEffect(() => { + subscribeToNavigationShortcuts(); + + // If we're coming from Plaid OAuth flow then we need to reuse the existing plaidLinkToken + if (isAuthenticatedWithPlaid()) { + return unsubscribeToNavigationShortcuts; } + BankAccounts.openPlaidBankLogin(allowDebit, bankAccountID); + return unsubscribeToNavigationShortcuts; - // Plaid bank accounts view + // disabling this rule, as we want this to run only on the first render + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + // If we are coming back from offline and we haven't authenticated with Plaid yet, we need to re-run our call to kick off Plaid + // previousNetworkState.current also makes sure that this doesn't run on the first render. + if (previousNetworkState.current && !isOffline && !isAuthenticatedWithPlaid()) { + BankAccounts.openPlaidBankLogin(allowDebit, bankAccountID); + } + previousNetworkState.current = isOffline; + }, [allowDebit, bankAccountID, isAuthenticatedWithPlaid, isOffline]); + + const plaidBankAccounts = lodashGet(plaidData, 'bankAccounts') || []; + const token = getPlaidLinkToken(); + const options = _.map(plaidBankAccounts, (account) => ({ + value: account.plaidAccountID, + label: `${account.addressName} ${account.mask}`, + })); + const {icon, iconSize} = getBankIcon(); + const plaidErrors = lodashGet(plaidData, 'errors'); + const plaidDataErrorMessage = !_.isEmpty(plaidErrors) ? _.chain(plaidErrors).values().first().value() : ''; + const bankName = lodashGet(plaidData, 'bankName'); + + // Plaid Link view + if (!plaidBankAccounts.length) { return ( - {!_.isEmpty(this.props.text) && {this.props.text}} - - - {bankName} - - - + + + )} + {Boolean(plaidDataErrorMessage) && {plaidDataErrorMessage}} + {Boolean(token) && !bankName && ( + { + Log.info('[PlaidLink] Success!'); + BankAccounts.openPlaidBankAccountSelector(publicToken, metadata.institution.name, allowDebit, bankAccountID); + }} + onError={(error) => { + Log.hmmm('[PlaidLink] Error: ', error.message); }} - value={this.props.selectedPlaidAccountID} + // User prematurely exited the Plaid flow + // eslint-disable-next-line react/jsx-props-no-multi-spaces + onExit={onExitPlaid} + receivedRedirectURI={receivedRedirectURI} /> - + )} ); } + + // Plaid bank accounts view + return ( + + {!_.isEmpty(text) && {text}} + + + {bankName} + + + + + + ); } AddPlaidBankAccount.propTypes = propTypes; AddPlaidBankAccount.defaultProps = defaultProps; - -export default compose( - withLocalize, - withNetwork(), - withOnyx({ - plaidLinkToken: { - key: ONYXKEYS.PLAID_LINK_TOKEN, - initWithStoredValues: false, - }, - }), -)(AddPlaidBankAccount); +AddPlaidBankAccount.displayName = 'AddPlaidBankAccount'; + +export default withOnyx({ + plaidLinkToken: { + key: ONYXKEYS.PLAID_LINK_TOKEN, + initWithStoredValues: false, + }, +})(AddPlaidBankAccount);