From d1c74b4eea348ad86afde70f4d18dca456905475 Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Thu, 26 Oct 2023 14:21:03 +0200 Subject: [PATCH] refactor: add hook WaitForUtxosToBeSpent --- src/components/Send/index.tsx | 74 ++++-------- src/components/fb/SpendFidelityBondModal.tsx | 117 +++++-------------- src/hooks/WaitForUtxosToBeSpent.ts | 64 ++++++++++ 3 files changed, 116 insertions(+), 139 deletions(-) create mode 100644 src/hooks/WaitForUtxosToBeSpent.ts diff --git a/src/components/Send/index.tsx b/src/components/Send/index.tsx index 4dd08a0e..45226622 100644 --- a/src/components/Send/index.tsx +++ b/src/components/Send/index.tsx @@ -7,18 +7,22 @@ import * as Api from '../../libs/JmWalletApi' import PageTitle from '../PageTitle' import Sprite from '../Sprite' import { ConfirmModal } from '../Modal' +import { scrollToTop } from '../../utils' import { PaymentConfirmModal } from '../PaymentConfirmModal' import FeeConfigModal, { FeeConfigSectionKey } from '../settings/FeeConfigModal' -import { useFeeConfigValues } from '../../hooks/Fees' -import { useReloadCurrentWalletInfo, useCurrentWalletInfo, CurrentWallet } from '../../context/WalletContext' + import { useServiceInfo, useReloadServiceInfo } from '../../context/ServiceInfoContext' import { useLoadConfigValue } from '../../context/ServiceConfigContext' +import { useReloadCurrentWalletInfo, useCurrentWalletInfo, CurrentWallet } from '../../context/WalletContext' +import { useFeeConfigValues } from '../../hooks/Fees' +import { useWaitForUtxosToBeSpent } from '../../hooks/WaitForUtxosToBeSpent' + import { routes } from '../../constants/routes' import { JM_MINIMUM_MAKERS_DEFAULT } from '../../constants/config' -import { scrollToTop } from '../../utils' -import { initialNumCollaborators } from './helpers' import { SendForm, SendFormValues } from './SendForm' +import { initialNumCollaborators } from './helpers' + const INITIAL_DESTINATION = null const INITIAL_SOURCE_JAR_INDEX = null const INITIAL_AMOUNT = null @@ -103,7 +107,7 @@ export default function Send({ wallet }: SendProps) { [feeConfigValues], ) - const [waitForUtxosToBeSpent, setWaitForUtxosToBeSpent] = useState([]) + const [waitForUtxosToBeSpent, setWaitForUtxosToBeSpent] = useState([]) const [paymentSuccessfulInfoAlert, setPaymentSuccessfulInfoAlert] = useState() const isOperationDisabled = useMemo( @@ -156,54 +160,22 @@ export default function Send({ wallet }: SendProps) { [wallet, setAlert, t], ) - // This callback is responsible for updating `waitForUtxosToBeSpent` while - // the wallet is synchronizing. The wallet needs some time after a tx is sent - // to reflect the changes internally. In order to show the actual balance, - // all outputs in `waitForUtxosToBeSpent` must have been removed from the - // wallet's utxo set. - useEffect( - function updateWaitForUtxosToBeSpentHook() { - if (waitForUtxosToBeSpent.length === 0) return - - const abortCtrl = new AbortController() - - // Delaying the poll requests gives the wallet some time to synchronize - // the utxo set and reduces amount of http requests - const initialDelayInMs = 250 - const timer = setTimeout(() => { - if (abortCtrl.signal.aborted) return - - reloadCurrentWalletInfo - .reloadUtxos({ signal: abortCtrl.signal }) - .then((res) => { - if (abortCtrl.signal.aborted) return - const outputs = res.utxos.map((it) => it.utxo) - const utxosStillPresent = waitForUtxosToBeSpent.filter((it) => outputs.includes(it)) - setWaitForUtxosToBeSpent([...utxosStillPresent]) - }) - - .catch((err) => { - if (abortCtrl.signal.aborted) return - - // Stop waiting for wallet synchronization on errors, but inform - // the user that loading the wallet info failed - setWaitForUtxosToBeSpent([]) - - const message = t('global.errors.error_reloading_wallet_failed', { - reason: err.message || t('global.errors.reason_unknown'), - }) - setAlert({ variant: 'danger', message }) - }) - }, initialDelayInMs) - - return () => { - abortCtrl.abort() - clearTimeout(timer) - } - }, - [waitForUtxosToBeSpent, reloadCurrentWalletInfo, t], + const waitForUtxosToBeSpentContext = useMemo( + () => ({ + waitForUtxosToBeSpent, + setWaitForUtxosToBeSpent, + onError: (error: any) => { + const message = t('global.errors.error_reloading_wallet_failed', { + reason: error.message || t('global.errors.reason_unknown'), + }) + setAlert({ variant: 'danger', message }) + }, + }), + [waitForUtxosToBeSpent, t], ) + useWaitForUtxosToBeSpent(waitForUtxosToBeSpentContext) + useEffect( function initialize() { if (isOperationDisabled) { diff --git a/src/components/fb/SpendFidelityBondModal.tsx b/src/components/fb/SpendFidelityBondModal.tsx index edc3c3d4..b3ad30ad 100644 --- a/src/components/fb/SpendFidelityBondModal.tsx +++ b/src/components/fb/SpendFidelityBondModal.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import * as rb from 'react-bootstrap' import { TFunction } from 'i18next' import { useTranslation } from 'react-i18next' -import { CurrentWallet, useReloadCurrentWalletInfo, Utxo, Utxos, WalletInfo } from '../../context/WalletContext' +import { CurrentWallet, Utxo, Utxos, WalletInfo } from '../../context/WalletContext' import * as Api from '../../libs/JmWalletApi' import * as fb from './utils' import Alert from '../Alert' @@ -14,6 +14,7 @@ import { useFeeConfigValues } from '../../hooks/Fees' import { isDebugFeatureEnabled } from '../../constants/debugFeatures' import { CopyButton } from '../CopyButton' import { LockInfoAlert } from './CreateFidelityBond' +import { useWaitForUtxosToBeSpent } from '../../hooks/WaitForUtxosToBeSpent' import styles from './SpendFidelityBondModal.module.css' type Input = { @@ -226,7 +227,6 @@ const RenewFidelityBondModal = ({ ...modalProps }: RenewFidelityBondModalProps) => { const { t } = useTranslation() - const reloadCurrentWalletInfo = useReloadCurrentWalletInfo() const feeConfigValues = useFeeConfigValues()[0] const [alert, setAlert] = useState() @@ -250,50 +250,21 @@ const RenewFidelityBondModal = ({ return walletInfo.data.utxos.utxos.find((utxo) => utxo.utxo === fidelityBondId) }, [walletInfo, fidelityBondId]) - // This callback is responsible for updating the loading state when the - // the payment is made. The wallet needs some time after a tx is sent - // to reflect the changes internally. All outputs in - // `waitForUtxosToBeSpent` must have been removed from the wallet - // for the payment to be considered done. - useEffect(() => { - if (waitForUtxosToBeSpent.length === 0) return - - const abortCtrl = new AbortController() - - // Delaying the poll requests gives the wallet some time to synchronize - // the utxo set and reduces amount of http requests - const initialDelayInMs = 1_000 - const timer = setTimeout(() => { - if (abortCtrl.signal.aborted) return - - reloadCurrentWalletInfo - .reloadUtxos({ signal: abortCtrl.signal }) - .then((res) => { - if (abortCtrl.signal.aborted) return - - const outputs = res.utxos.map((it) => it.utxo) - const utxosStillPresent = waitForUtxosToBeSpent.filter((it) => outputs.includes(it)) - setWaitForUtxosToBeSpent([...utxosStillPresent]) + const waitForUtxosToBeSpentContext = useMemo( + () => ({ + waitForUtxosToBeSpent, + setWaitForUtxosToBeSpent, + onError: (error: any) => { + const message = t('global.errors.error_reloading_wallet_failed', { + reason: error.message || t('global.errors.reason_unknown'), }) - .catch((err) => { - if (abortCtrl.signal.aborted) return - - // Stop waiting for wallet synchronization on errors, but inform - // the user that loading the wallet info failed - setWaitForUtxosToBeSpent([]) - - const message = t('global.errors.error_reloading_wallet_failed', { - reason: err.message || t('global.errors.reason_unknown'), - }) - setAlert({ variant: 'danger', message }) - }) - }, initialDelayInMs) + setAlert({ variant: 'danger', message }) + }, + }), + [waitForUtxosToBeSpent, t], + ) - return () => { - abortCtrl.abort() - clearTimeout(timer) - } - }, [waitForUtxosToBeSpent, reloadCurrentWalletInfo, t]) + useWaitForUtxosToBeSpent(waitForUtxosToBeSpentContext) const yearsRange = useMemo(() => { if (isDebugFeatureEnabled('allowCreatingExpiredFidelityBond')) { @@ -361,7 +332,7 @@ const RenewFidelityBondModal = ({ return <>{t('earn.fidelity_bond.renew.text_button_submit')} }, [isSending, txInfo, t]) - const onSelectedDateChanged = useCallback((date) => { + const onSelectedDateChanged = useCallback((date: Api.Lockdate | null) => { setTimelockedAddress(undefined) setLockDate(date ?? undefined) }, []) @@ -558,7 +529,6 @@ const SpendFidelityBondModal = ({ ...modalProps }: SpendFidelityBondModalProps) => { const { t } = useTranslation() - const reloadCurrentWalletInfo = useReloadCurrentWalletInfo() const feeConfigValues = useFeeConfigValues()[0] const [alert, setAlert] = useState() @@ -582,50 +552,21 @@ const SpendFidelityBondModal = ({ return walletInfo.data.utxos.utxos.find((utxo) => utxo.utxo === fidelityBondId) }, [walletInfo, fidelityBondId]) - // This callback is responsible for updating the loading state when the - // the payment is made. The wallet needs some time after a tx is sent - // to reflect the changes internally. All outputs in - // `waitForUtxosToBeSpent` must have been removed from the wallet - // for the payment to be considered done. - useEffect(() => { - if (waitForUtxosToBeSpent.length === 0) return - - const abortCtrl = new AbortController() - - // Delaying the poll requests gives the wallet some time to synchronize - // the utxo set and reduces amount of http requests - const initialDelayInMs = 1_000 - const timer = setTimeout(() => { - if (abortCtrl.signal.aborted) return - - reloadCurrentWalletInfo - .reloadUtxos({ signal: abortCtrl.signal }) - .then((res) => { - if (abortCtrl.signal.aborted) return - - const outputs = res.utxos.map((it) => it.utxo) - const utxosStillPresent = waitForUtxosToBeSpent.filter((it) => outputs.includes(it)) - setWaitForUtxosToBeSpent([...utxosStillPresent]) + const waitForUtxosToBeSpentContext = useMemo( + () => ({ + waitForUtxosToBeSpent, + setWaitForUtxosToBeSpent, + onError: (error: any) => { + const message = t('global.errors.error_reloading_wallet_failed', { + reason: error.message || t('global.errors.reason_unknown'), }) - .catch((err) => { - if (abortCtrl.signal.aborted) return - - // Stop waiting for wallet synchronization on errors, but inform - // the user that loading the wallet info failed - setWaitForUtxosToBeSpent([]) - - const message = t('global.errors.error_reloading_wallet_failed', { - reason: err.message || t('global.errors.reason_unknown'), - }) - setAlert({ variant: 'danger', message }) - }) - }, initialDelayInMs) + setAlert({ variant: 'danger', message }) + }, + }), + [waitForUtxosToBeSpent, t], + ) - return () => { - abortCtrl.abort() - clearTimeout(timer) - } - }, [waitForUtxosToBeSpent, reloadCurrentWalletInfo, t]) + useWaitForUtxosToBeSpent(waitForUtxosToBeSpentContext) const onPrimaryButtonClicked = () => { if (isLoading) return diff --git a/src/hooks/WaitForUtxosToBeSpent.ts b/src/hooks/WaitForUtxosToBeSpent.ts new file mode 100644 index 00000000..b54da5cd --- /dev/null +++ b/src/hooks/WaitForUtxosToBeSpent.ts @@ -0,0 +1,64 @@ +import { useEffect } from 'react' +import { useReloadCurrentWalletInfo } from '../context/WalletContext' +import { UtxoId } from '../libs/JmWalletApi' + +// Delaying the poll requests gives the wallet some time to synchronize +// the utxo set and reduces amount of http requests +const DEFAUL_DELAY: Milliseconds = 1_000 + +interface WaitForUtxosToBeSpentArgs { + waitForUtxosToBeSpent: UtxoId[] + setWaitForUtxosToBeSpent: (utxos: UtxoId[]) => void + onError: (error: any) => void + delay?: Milliseconds + resetOnErrors?: boolean +} + +// This callback is responsible for updating the utxo array when a +// payment is made. The wallet needs some time after a tx is sent +// to reflect the changes internally. All outputs given in +// `waitForUtxosToBeSpent` must have been removed from the wallet +// for a payment to be considered done. +export const useWaitForUtxosToBeSpent = ({ + waitForUtxosToBeSpent, + setWaitForUtxosToBeSpent, + onError, + delay = DEFAUL_DELAY, + resetOnErrors = true, +}: WaitForUtxosToBeSpentArgs): void => { + const reloadCurrentWalletInfo = useReloadCurrentWalletInfo() + return useEffect(() => { + if (waitForUtxosToBeSpent.length === 0) return + + const abortCtrl = new AbortController() + + const timer = setTimeout(() => { + if (abortCtrl.signal.aborted) return + + reloadCurrentWalletInfo + .reloadUtxos({ signal: abortCtrl.signal }) + .then((res) => { + if (abortCtrl.signal.aborted) return + + const outputs = res.utxos.map((it) => it.utxo) + const utxosStillPresent = waitForUtxosToBeSpent.filter((it) => outputs.includes(it)) + + // updating the utxos array will trigger a recursive call + setWaitForUtxosToBeSpent([...utxosStillPresent]) + }) + .catch((error: any) => { + if (abortCtrl.signal.aborted) return + if (resetOnErrors) { + // Stop waiting for wallet synchronization on errors + setWaitForUtxosToBeSpent([]) + } + onError(error) + }) + }, delay) + + return () => { + abortCtrl.abort() + clearTimeout(timer) + } + }, [waitForUtxosToBeSpent, setWaitForUtxosToBeSpent, resetOnErrors, onError, delay, reloadCurrentWalletInfo]) +}