ensure fiat currency selector cannot hang
This commit is contained in:
@@ -92,6 +92,7 @@ export default function AnalyticsCreateBudgetPage() {
|
||||
onSelectCurrency: async code => {
|
||||
const response = await selectCurrency(code);
|
||||
if (response?.didWork) setAmountValue('');
|
||||
return response;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -659,6 +659,7 @@ export default function useContactPayment({
|
||||
async code => {
|
||||
const response = await selectCurrency(code);
|
||||
if (response?.didWork) setAmountValue('');
|
||||
return response;
|
||||
},
|
||||
[selectCurrency],
|
||||
);
|
||||
|
||||
@@ -22,8 +22,6 @@ import {
|
||||
INSET_WINDOW_WIDTH,
|
||||
} from '../../../../constants/theme';
|
||||
import FullLoadingScreen from '../../../../functions/CustomElements/loadingScreen';
|
||||
import loadNewFiatData from '../../../../functions/saveAndUpdateFiatData';
|
||||
import { useKeysContext } from '../../../../../context-store/keys';
|
||||
|
||||
export default function DisplayCurrencySelect({
|
||||
currentCurrency,
|
||||
@@ -36,7 +34,6 @@ export default function DisplayCurrencySelect({
|
||||
const { masterInfoObject } = useGlobalContextProvider();
|
||||
const { theme, darkModeType } = useGlobalThemeContext();
|
||||
const { screenDimensions } = useAppStatus();
|
||||
const { contactsPrivateKey, publicKey } = useKeysContext();
|
||||
const { backgroundColor, backgroundOffset, textColor } = GetThemeColors();
|
||||
const normalizedCurrentCurrency = normalizeDisplayCurrency(currentCurrency);
|
||||
const deviceCurrency = (
|
||||
@@ -67,28 +64,28 @@ export default function DisplayCurrencySelect({
|
||||
if (hasSelected.current) return;
|
||||
hasSelected.current = true;
|
||||
setIsLoadingNewRate(true);
|
||||
const normalizedCode = normalizeDisplayCurrency(currency);
|
||||
currency !== 'SATS' &&
|
||||
(await loadNewFiatData(
|
||||
normalizedCode,
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
masterInfoObject,
|
||||
));
|
||||
|
||||
// The parent (useDisplayCurrencyController.selectCurrency) owns the single
|
||||
// fetch, its hard timeout, rate caching, and ErrorScreen navigation on
|
||||
// failure. We just reflect its result. Because the timeout lives in the hook,
|
||||
// the hook stops loading at the same moment this await resolves — no state
|
||||
// mismatch where the picker gives up but the hook keeps loading.
|
||||
const response = await onSelectCurrency?.(currency);
|
||||
|
||||
if (!isMounted.current) return;
|
||||
handleBackPressFunction(() => {
|
||||
navigate.goBack();
|
||||
onSelectCurrency?.(currency);
|
||||
});
|
||||
|
||||
// Failure or timeout: clear the loader and reset so the picker returns to an
|
||||
// interactive state instead of relying on unmount to hide the loader.
|
||||
if (!response || response.didWork === false) {
|
||||
hasSelected.current = false;
|
||||
setIsLoadingNewRate(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Success: dismiss the picker.
|
||||
handleBackPressFunction(() => navigate.goBack());
|
||||
},
|
||||
[
|
||||
handleBackPressFunction,
|
||||
navigate,
|
||||
onSelectCurrency,
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
masterInfoObject,
|
||||
],
|
||||
[handleBackPressFunction, navigate, onSelectCurrency],
|
||||
);
|
||||
|
||||
const pinnedRows = useMemo(() => {
|
||||
|
||||
@@ -88,6 +88,7 @@ export default function CreateGift(props) {
|
||||
onSelectCurrency: async code => {
|
||||
const response = await selectCurrency(code);
|
||||
if (response?.didWork) setAmountValue('');
|
||||
return response;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ export default function PayLinkAmountInput({
|
||||
onSelectCurrency: async code => {
|
||||
const response = await selectCurrency(code);
|
||||
if (response?.didWork) setAmountValue('');
|
||||
return response;
|
||||
},
|
||||
}),
|
||||
[navigate, displayCurrency, selectCurrency],
|
||||
|
||||
@@ -76,6 +76,7 @@ export default function PoolAmountInput({
|
||||
onSelectCurrency: async code => {
|
||||
const response = await selectCurrency(code);
|
||||
if (response?.didWork) setAmountValue('');
|
||||
return response;
|
||||
},
|
||||
}),
|
||||
[navigate, displayCurrency, selectCurrency],
|
||||
|
||||
@@ -289,6 +289,7 @@ export default function ContributeToPoolHalfModal({
|
||||
onSelectCurrency: async code => {
|
||||
const response = await selectCurrency(code);
|
||||
if (response?.didWork) setAmountValue('');
|
||||
return response;
|
||||
},
|
||||
}),
|
||||
[displayCurrency, navigate, selectCurrency],
|
||||
|
||||
@@ -145,6 +145,7 @@ export default function EditReceivePaymentInformation(props) {
|
||||
onSelectCurrency: async code => {
|
||||
const response = await selectCurrency(code);
|
||||
if (response?.didWork) setAmountValue('');
|
||||
return response;
|
||||
},
|
||||
});
|
||||
}, [displayCurrency, isKeyboardFocused, navigate, selectCurrency]);
|
||||
|
||||
@@ -1417,16 +1417,18 @@ export default function SendPaymentScreen(props) {
|
||||
currentCurrency: displayCurrency,
|
||||
onSelectCurrency: async code => {
|
||||
const response = await selectCurrency(code);
|
||||
if (!response?.didWork) return;
|
||||
if (!canEditAmount) return;
|
||||
setPaymentInfo(prev => ({
|
||||
...prev,
|
||||
sendAmount: '',
|
||||
feeQuote: undefined,
|
||||
swapPaymentQuote: undefined,
|
||||
paymentFee: 0,
|
||||
supportFee: 0,
|
||||
}));
|
||||
if (!response?.didWork) return response;
|
||||
if (canEditAmount) {
|
||||
setPaymentInfo(prev => ({
|
||||
...prev,
|
||||
sendAmount: '',
|
||||
feeQuote: undefined,
|
||||
swapPaymentQuote: undefined,
|
||||
paymentFee: 0,
|
||||
supportFee: 0,
|
||||
}));
|
||||
}
|
||||
return response;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -387,6 +387,7 @@ export default function StablecoinSendScreen() {
|
||||
onSelectCurrency: async code => {
|
||||
const response = await selectCurrency(code);
|
||||
if (response?.didWork) setRawInput('');
|
||||
return response;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -88,6 +88,7 @@ export default function CustomInputHalfModal(props) {
|
||||
onSelectCurrency: async code => {
|
||||
const response = await selectCurrency(code);
|
||||
if (response?.didWork) setAmountValue('');
|
||||
return response;
|
||||
},
|
||||
}),
|
||||
[displayCurrency, navigate, selectCurrency],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { useFocusEffect, useNavigation } from '@react-navigation/native';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useKeysContext } from '../../context-store/keys';
|
||||
import { useToast } from '../../context-store/toastManager';
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
normalizeDisplayCurrency,
|
||||
} from '../functions/displayCurrency';
|
||||
|
||||
// Hard cap on a single rate fetch, well under loadNewFiatData's ~3 min retry budget.
|
||||
const CURRENCY_RATE_TIMEOUT_MS = 30000;
|
||||
|
||||
export default function useDisplayCurrencyController({
|
||||
initialCurrency,
|
||||
fiatStats,
|
||||
@@ -79,12 +82,29 @@ export default function useDisplayCurrencyController({
|
||||
|
||||
try {
|
||||
setIsLoadingRate(true);
|
||||
const response = await loadNewFiatData(
|
||||
normalizedCode,
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
masterInfoObject,
|
||||
);
|
||||
|
||||
// Hard-bound the fetch so isLoadingRate (and the currency picker that
|
||||
// awaits this call) can never stay up for loadNewFiatData's full retry
|
||||
// budget (~3 min). On timeout we take the same failure path and clear the
|
||||
// loader here; the abandoned fetch may still warm the rate cache in the
|
||||
// background. Keeping the timeout in the hook keeps the picker and the hook
|
||||
// in sync — they resolve from the same bounded promise, no state mismatch.
|
||||
let timeoutId;
|
||||
const response = await Promise.race([
|
||||
loadNewFiatData(
|
||||
normalizedCode,
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
masterInfoObject,
|
||||
),
|
||||
new Promise(resolve => {
|
||||
timeoutId = setTimeout(
|
||||
() => resolve({ didWork: false }),
|
||||
CURRENCY_RATE_TIMEOUT_MS,
|
||||
);
|
||||
}),
|
||||
]);
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.didWork) throw new Error('error loading fiat data');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user