I'm building a React Native application that is polling for wallet balances every 6 seconds. I've setup a timer which I am using to 1) call ONCE initially for balance (ie. not wait 6 seconds for the interval in my timer to run) and 2) poll as normal after that.
The problem I'm having is the user can change the state while the polling is happening but because the interval is already running what happens is the user updates the state then the polling code overwrites it.
For example given assetBalances
of:
{ lorem: { balance: 100 } }
If the user were to change the wallet name from "lorem"
to "ipsum"
I'd momentarily be left with assetBalances
of:
{ ipsum: { balance: 100 } }
Which would then quickly become the following when the poll has ran. (The reason it adds to the object is because I spread with ...
as you can see in network-scan.ts
):
{ lorem: { balance: 100 }, ipsum: { balance: 100 } }
I've tried using a custom usePrevious
hook which compares the last prop to current prop and then I conditionally call the setAsyncValue
in network-scan.ts
but that didn't work.
I need a way to CANCEL the running timer.start
instance if x prop changes (x being assetBalances
in my first case).
On a side note I'm also having an issue where for some reason when there's lots of state changes the timer gets called multiple times which causes loads of intervals to run parallelly, which I don't understand because I'm calling timer.stop()
at the start of every useEffect re-render - why?
If anyone has any ideas as to how I can solve these issues I'd really appreciate it! Or is there a much easier way to do what I'm trying to do? Here's some code for context:
timer.ts
const timer = { running: false, timeout: false, cb: function () {}, start: function (cb: any, iv: number, cbAtEnd?: any) { const elm = this; clearInterval(this.timeout); this.running = true; if (cb) this.cb = cb; this.timeout = setTimeout(function () { elm.execute(elm); if (cbAtEnd) cbAtEnd(); }, iv); }, execute: function (e: any) { if (!e.running) return false; e.cb(); e.start(); }, stop: function () { this.running = false; },};
polling.ts
export default function useNetworkPolling(): void { const { state: { wallets, userToken, assetBalances, selectedWallet, connectedNetwork, networkPollingInterval, networkSpecificTokens, }, setAsyncValue, } = useContext(LocalContext); const intervalTime = networkPollingInterval // default 6s const address = useGetWalletAddress(selectedWallet); useEffect(() => { function scan() { console.log(`Scan now! - (Interval time is: ${intervalTime}ms) [from useNetworkPolling]`); const filteredTokens = getTokensFilteredByTrackedStatus( connectedNetwork, networkSpecificTokens ); const allTokens = CORE_TOKENS.concat(filteredTokens); handleNetworkScan( allTokens, JSON.parse(assetBalances), address, setAsyncValue, connectedNetwork, selectedWallet ); console.log(JSON.stringify(JSON.parse(assetBalances), null, 2)); } timer.stop(); // Make sure the previous one is stopped so two never run at the same time if (userToken && JSON.parse(wallets).length) { // Start off with an immediate one-off call timer.start(scan, 1, () => { timer.stop(); // Pass a callback into the start function which stops the first timer timer.start(scan, intervalTime); // Start polling with the interval }); } }, [ address, userToken, networkPollingInterval, wallets, connectedNetwork, networkSpecificTokens, assetBalances, selectedWallet, ]);}
network-scan.ts
export default async function handleNetworkScan( coreAndTrackedTokens: Token[], assetBalances: any, address: string, setAsyncValue: (key: string, value: string) => Promise<void>, connectedNetwork: string, selectedWallet: string) { let updatedBalances = assetBalances[selectedWallet]; for (const token of coreAndTrackedTokens) { try { // Redacted logic that updates updatedBalances if success... } catch (e) { // Redacted logic that updates updatedBalances if error... } } setAsyncValue('assetBalances', JSON.stringify({ ...assetBalances, [selectedWallet]: updatedBalances }) );}