From e3b3b7643030cc4255f1b32eff2078b96a741318 Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Date: Mon, 13 Oct 2025 17:34:50 -0400 Subject: [PATCH] fix(load): Load BCH & SLP info in the background --- src/App.js | 55 ++++++++++++++++--- .../app-body/bch-send/balance-card.js | 53 ++++++++++++------ .../app-body/bch-send/refresh-balance.js | 3 + .../bch-send/refresh-bch-balance-button.js | 6 +- src/components/app-body/bch-send/send-card.js | 6 +- src/components/app-body/slp-tokens/index.js | 36 +++++++++--- .../app-body/slp-tokens/refresh-tokens.js | 10 +++- src/components/starter-views.js | 2 +- src/hooks/state.js | 28 +++++++++- src/services/async-load.js | 9 +++ 10 files changed, 167 insertions(+), 41 deletions(-) diff --git a/src/App.js b/src/App.js index be39888..090fbad 100644 --- a/src/App.js +++ b/src/App.js @@ -65,6 +65,44 @@ function App (props) { return false }, [appData, addToModal]) + /** + * Run background process to get bch and slp balance. + * Also update the background state process. + * On error this function should trigger a info modal notifying the errors. + */ + const backgroundAsync = useCallback(async (asyncLoad, walletTemp) => { + try { + appData.setModalBody(['Getting BCH balance in background!.']) + // Get Wallet Balance + await asyncLoad.getWalletBchBalance(walletTemp, appData.updateBchWalletState, appData) + // Update Background state + appData.updateBackGroundInitState({ bchInitLoaded: true }) + + // Get SLP Balance + appData.setModalBody(['Getting SLP tokens in background!.']) + await asyncLoad.getSlpTokenBalances(walletTemp, appData.updateBchWalletState, appData) + + // Update Background state + appData.updateBackGroundInitState({ slpInitLoaded: true, asyncBackgroundFinished: true }) + } catch (err) { + console.log('App.js backgroundAsync() error!', err) + + appData.updateBackGroundInitState({ asyncBackgroundFinished: true }) + + addToModal(`Error: ${err.message}`, appData) + addToModal('Try selecting a different back end server using the drop-down menu at the bottom of the app.\'', appData) + + // Update Modal State + appData.setHideSpinner(true) + appData.setShowStartModal(true) + appData.setDenyClose(false) + + // Update the startup state. + appData.setAsyncInitFinished(true) + appData.setAsyncInitSucceeded(false) + } + }, [appData, addToModal]) + /** Load all required data before component start. */ useEffect(() => { async function asyncEffect () { @@ -95,14 +133,6 @@ function App (props) { appData.setWallet(walletTemp) // appData.updateBchWalletState({ walletObj: walletTemp.walletInfo, appData }) - // Get the BCH balance of the wallet. - addToModal('Getting BCH balance', appData) - await asyncLoad.getWalletBchBalance(walletTemp, appData.updateBchWalletState, appData) - - // Get the SLP tokens held by the wallet. - addToModal('Getting SLP tokens', appData) - await asyncLoad.getSlpTokenBalances(walletTemp, appData.updateBchWalletState, appData) - // Get the BCH spot price addToModal('Getting BCH spot price in USD', appData) await asyncLoad.getUSDExchangeRate(walletTemp, appData.updateBchWalletState, appData) @@ -115,6 +145,13 @@ function App (props) { appData.setAsyncInitFinished(true) appData.setAsyncInitSucceeded(true) console.log('App.js useEffect() startup finished successfully') + + backgroundAsync(asyncLoad, walletTemp) + // Get the BCH balance of the wallet. + // addToModal('Getting BCH balance', appData) + + // Get the SLP tokens held by the wallet. + // addToModal('Getting SLP tokens', appData) } catch (err) { const errModalBody = [ `Error: ${err.message}`, @@ -134,7 +171,7 @@ function App (props) { } } asyncEffect() - }, [appData, addToModal, isSignleView]) + }, [appData, addToModal, isSignleView, backgroundAsync]) return ( <> diff --git a/src/components/app-body/bch-send/balance-card.js b/src/components/app-body/bch-send/balance-card.js index 3fa541c..62b5a46 100644 --- a/src/components/app-body/bch-send/balance-card.js +++ b/src/components/app-body/bch-send/balance-card.js @@ -4,7 +4,7 @@ // Global npm libraries import React from 'react' -import { Container, Row, Col, Card } from 'react-bootstrap' +import { Container, Row, Col, Card, Spinner } from 'react-bootstrap' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCoins } from '@fortawesome/free-solid-svg-icons' @@ -15,6 +15,11 @@ const BalanceCard = (props) => { const sats = appData.bchWalletState.bchBalance const bchBalance = bchjs.BitcoinCash.toBitcoinCash(sats) const usdBalance = bchjs.Util.floor2(bchBalance * appData.bchWalletState.bchUsdPrice) + const { bchInitLoaded, asyncBackgroundFinished } = appData.asyncBackGroundInitState + + // Background bch data loaded finished + const backgroundDataLoaded = bchInitLoaded || asyncBackgroundFinished + const backgroundDataError = !bchInitLoaded && asyncBackgroundFinished return ( <> @@ -25,25 +30,37 @@ const BalanceCard = (props) => {
- - - - USD: ${usdBalance} - - + {bchInitLoaded && ( + + + + USD: ${usdBalance} + + - - - BCH: {bchBalance} - - + + + BCH: {bchBalance} + + - - - Satoshis: {sats} - - - + + + Satoshis: {sats} + + + )} + {backgroundDataError && ( + + Balance could not be loaded! + + )} + + {!backgroundDataLoaded && ( +
+ +
+ )} diff --git a/src/components/app-body/bch-send/refresh-balance.js b/src/components/app-body/bch-send/refresh-balance.js index b95163f..ec33e89 100644 --- a/src/components/app-body/bch-send/refresh-balance.js +++ b/src/components/app-body/bch-send/refresh-balance.js @@ -50,6 +50,9 @@ export default function RefreshBchBalance (props) { // Get the latest balance of the wallet. const newBalance = await wallet.getBalance({ bchAddress: cashAddr }) + // if bchInitLoaded is 'false', them set as true , to show the new balance. + appData.updateBackGroundInitState({ bchInitLoaded: true }) + addToModal('Updating BCH per USD price...') const bchUsdPrice = await wallet.getUsd() diff --git a/src/components/app-body/bch-send/refresh-bch-balance-button.js b/src/components/app-body/bch-send/refresh-bch-balance-button.js index 80d7044..d63c44f 100644 --- a/src/components/app-body/bch-send/refresh-bch-balance-button.js +++ b/src/components/app-body/bch-send/refresh-bch-balance-button.js @@ -16,6 +16,10 @@ import RefreshBchBalance from './refresh-balance' function RefreshBchBalanceButton (props) { // Dependency injections of props const appData = props.appData + const { bchInitLoaded, asyncBackgroundFinished } = appData.asyncBackGroundInitState + + // Background bch data loaded finished + const backgroundDataLoaded = bchInitLoaded || asyncBackgroundFinished // Child function references const refreshBchBalanceRef = useRef() @@ -28,7 +32,7 @@ function RefreshBchBalanceButton (props) { return ( <> - diff --git a/src/components/app-body/bch-send/send-card.js b/src/components/app-body/bch-send/send-card.js index 9c85126..43ffbac 100644 --- a/src/components/app-body/bch-send/send-card.js +++ b/src/components/app-body/bch-send/send-card.js @@ -15,6 +15,7 @@ import RefreshBchBalance from './refresh-balance' function SendCard (props) { // Dependency injection through props const appData = props.appData + const { bchInitLoaded, asyncBackgroundFinished } = appData.asyncBackGroundInitState // Modal State const [modalBody, setModalBody] = useState([]) @@ -29,6 +30,9 @@ function SendCard (props) { const [oppositeUnits, setOppositeUnits] = useState('BCH') const [oppositeQty, setOppositeQty] = useState(0) + // Background bch data loaded finished + const backgroundDataLoaded = bchInitLoaded || asyncBackgroundFinished + // Child function references const refreshBchBalanceRef = useRef() @@ -318,7 +322,7 @@ function SendCard (props) { - + diff --git a/src/components/app-body/slp-tokens/index.js b/src/components/app-body/slp-tokens/index.js index 9fa58e6..67bae78 100644 --- a/src/components/app-body/slp-tokens/index.js +++ b/src/components/app-body/slp-tokens/index.js @@ -13,12 +13,17 @@ import TokenCard from './token-card' import RefreshTokenBalance from './refresh-tokens' const SlpTokens = (props) => { - const [appData, setAppData] = useState(props.appData) + const { appData } = props const [iconsAreLoaded, setIconsAreLoaded] = useState(false) const [dataAreLoaded, setDataAreLoaded] = useState(false) const [tokens, setTokens] = useState([]) const refreshTokenButtonRef = React.useRef() + const { slpInitLoaded, asyncBackgroundFinished } = props.appData.asyncBackGroundInitState + + // Background bch data loaded finished + const backgroundDataLoaded = slpInitLoaded || asyncBackgroundFinished + const backgroundDataError = !slpInitLoaded && asyncBackgroundFinished // Update the tokens state when the appData changes useEffect(() => { @@ -31,8 +36,7 @@ const SlpTokens = (props) => { // within the wallet app. // This function triggers the on-click function within the refresh-tokens.js button. const refreshTokens = async () => { - const newAppData = await refreshTokenButtonRef.current.handleRefreshTokenBalance() - setAppData(newAppData) + await refreshTokenButtonRef.current.handleRefreshTokenBalance() } // Get Cid from url @@ -135,6 +139,7 @@ const SlpTokens = (props) => { const loadData = useCallback(async () => { const tokens = appData.bchWalletState.slpTokens + console.log('tokens', tokens) setTokens(tokens) await lazyLoadTokenData(tokens) await lazyLoadMutableData(tokens) @@ -142,8 +147,10 @@ const SlpTokens = (props) => { // Start to load the token icons when the component is mounted useEffect(() => { - loadData() - }, [loadData]) + if (slpInitLoaded) { + loadData() + } + }, [loadData, slpInitLoaded]) // Generate the token cards for each token in the wallet. const generateCards = () => { @@ -178,7 +185,15 @@ const SlpTokens = (props) => { {/** Show spinner info if tokens are loaded but data is not loaded */ - !dataAreLoaded && ( + !backgroundDataLoaded && !backgroundDataError && ( +
+ Loading Tokens + +
+ ) + } + {/** Show spinner info if tokens are loaded but data is not loaded */ + !backgroundDataError && !dataAreLoaded && tokens.length > 0 && (
Loading Token Data @@ -186,7 +201,7 @@ const SlpTokens = (props) => { ) } {/** Show spinner info if tokens are loaded but icons are not loaded */ - dataAreLoaded && !iconsAreLoaded && ( + backgroundDataLoaded && dataAreLoaded && !iconsAreLoaded && (
Loading Token Icons @@ -202,11 +217,16 @@ const SlpTokens = (props) => { {generateCards()} {/** Display a message if no tokens are found */} - {tokens.length === 0 && ( + {backgroundDataLoaded && !backgroundDataError && tokens.length === 0 && ( No tokens found in wallet )} + {backgroundDataError && ( + + Tokens could not be loaded! + + )} diff --git a/src/components/app-body/slp-tokens/refresh-tokens.js b/src/components/app-body/slp-tokens/refresh-tokens.js index 80a2a20..dbab17d 100644 --- a/src/components/app-body/slp-tokens/refresh-tokens.js +++ b/src/components/app-body/slp-tokens/refresh-tokens.js @@ -18,6 +18,10 @@ function RefreshTokenBalance ({ appData: initialAppData, ref, lazyLoadTokenIcons const [modalBody, setModalBody] = useState([]) const [hideSpinner, setHideSpinner] = useState(false) const [hideWaitingModal, setHideWaitingModal] = useState(true) + const { slpInitLoaded, asyncBackgroundFinished } = initialAppData.asyncBackGroundInitState + + // Background bch data loaded finished + const backgroundDataLoaded = slpInitLoaded || asyncBackgroundFinished // Add a new line to the waiting modal. const addToModal = (inStr) => { @@ -58,6 +62,10 @@ function RefreshTokenBalance ({ appData: initialAppData, ref, lazyLoadTokenIcons appData.updateBchWalletState({ walletObj: walletState, appData }) const newAppData = { ...appData, bchWalletState: walletState } + + // if slpInitLoaded is 'false', them set as true , to show the new balance. + appData.updateBackGroundInitState({ slpInitLoaded: true }) + // Update state setHideWaitingModal(true) setAppData(newAppData) @@ -82,7 +90,7 @@ function RefreshTokenBalance ({ appData: initialAppData, ref, lazyLoadTokenIcons return ( <> - diff --git a/src/components/starter-views.js b/src/components/starter-views.js index 1ab1833..899500e 100644 --- a/src/components/starter-views.js +++ b/src/components/starter-views.js @@ -21,7 +21,7 @@ export function UninitializedView (props = {}) { /> { appData.asyncInitFinished - ? + ? <>
: null } diff --git a/src/hooks/state.js b/src/hooks/state.js index f8cd40a..43bd090 100644 --- a/src/hooks/state.js +++ b/src/hooks/state.js @@ -36,6 +36,10 @@ function useAppState () { const [denyClose, setDenyClose] = useState(false) const [isSingleView, setIsSingleView] = useState(false) + // Background process state + const [asyncBackGroundInitState, setAsyncBackGroundInitState] = useState({ + bchInitLoaded: false, slpInitLoaded: false, asyncBackgroundFinished: false + }) // The wallet state makes this a true progressive web app (PWA). As // balances, UTXOs, and tokens are retrieved, this state is updated. @@ -96,6 +100,25 @@ function useAppState () { } } + // Update background state + function updateBackGroundInitState (inObj = {}) { + try { + setAsyncBackGroundInitState(oldState => { + // console.log('background old state: ', oldState) + + const state = Object.assign({}, oldState, inObj) + // console.log('background state: ', state) + + return state + }) + + // console.log(`New wallet state: ${JSON.stringify(bchWalletState, null, 2)}`) + } catch (err) { + console.error('Error in App.js updateBackGroundInitState()') + throw err + } + } + return { serverUrl, setServerUrl, @@ -129,8 +152,9 @@ function useAppState () { appUtil: new AppUtil(), currentPath: location.pathname, setIsSingleView, - isSingleView - + isSingleView, + asyncBackGroundInitState, + updateBackGroundInitState } } diff --git a/src/services/async-load.js b/src/services/async-load.js index 7b041d9..2b5bcec 100644 --- a/src/services/async-load.js +++ b/src/services/async-load.js @@ -76,6 +76,11 @@ class AsyncLoad { // Get the BCH balance of the wallet. async getWalletBchBalance (wallet, updateBchWalletState, appData) { try { + /* // Force error for development + await sleep(6000) + throw new Error('getWalletBchBalance error') + */ + // Get the BCH balance of the wallet. const bchBalance = await wallet.getBalance({ bchAddress: wallet.walletInfo.cashAddress }) @@ -111,6 +116,10 @@ class AsyncLoad { // Get a list of SLP tokens held by the wallet. async getSlpTokenBalances (wallet, updateBchWalletState, appData) { try { + /* // Force error for development + await sleep(6000) + throw new Error('getSlpTokenBalances error') + */ // Get token information from the wallet. This will also initialize the UTXO store. const slpTokens = await wallet.listTokens(wallet.walletInfo.cashAddress) // console.log('slpTokens: ', slpTokens)