Skip to content

Commit

Permalink
refactor: add hook WaitForUtxosToBeSpent
Browse files Browse the repository at this point in the history
  • Loading branch information
theborakompanioni committed Dec 20, 2023
1 parent 7fdaa21 commit d1c74b4
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 139 deletions.
74 changes: 23 additions & 51 deletions src/components/Send/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -103,7 +107,7 @@ export default function Send({ wallet }: SendProps) {
[feeConfigValues],
)

const [waitForUtxosToBeSpent, setWaitForUtxosToBeSpent] = useState([])
const [waitForUtxosToBeSpent, setWaitForUtxosToBeSpent] = useState<Api.UtxoId[]>([])
const [paymentSuccessfulInfoAlert, setPaymentSuccessfulInfoAlert] = useState<SimpleAlert>()

const isOperationDisabled = useMemo(
Expand Down Expand Up @@ -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) {
Expand Down
117 changes: 29 additions & 88 deletions src/components/fb/SpendFidelityBondModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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 = {
Expand Down Expand Up @@ -226,7 +227,6 @@ const RenewFidelityBondModal = ({
...modalProps
}: RenewFidelityBondModalProps) => {
const { t } = useTranslation()
const reloadCurrentWalletInfo = useReloadCurrentWalletInfo()
const feeConfigValues = useFeeConfigValues()[0]

const [alert, setAlert] = useState<SimpleAlert>()
Expand All @@ -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')) {
Expand Down Expand Up @@ -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)
}, [])
Expand Down Expand Up @@ -558,7 +529,6 @@ const SpendFidelityBondModal = ({
...modalProps
}: SpendFidelityBondModalProps) => {
const { t } = useTranslation()
const reloadCurrentWalletInfo = useReloadCurrentWalletInfo()
const feeConfigValues = useFeeConfigValues()[0]

const [alert, setAlert] = useState<SimpleAlert>()
Expand All @@ -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
Expand Down
64 changes: 64 additions & 0 deletions src/hooks/WaitForUtxosToBeSpent.ts
Original file line number Diff line number Diff line change
@@ -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])
}

0 comments on commit d1c74b4

Please sign in to comment.