Commit 5afddb2e authored by Moody Salem's avatar Moody Salem

Reduce the spam of redux events

parent 3b2c21e2
import { useEffect } from 'react' import { useEffect, useState } from 'react'
import { useWeb3React } from '../../hooks' import { useDebounce, useWeb3React } from '../../hooks'
import { updateBlockNumber } from './actions' import { updateBlockNumber } from './actions'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
...@@ -7,17 +7,27 @@ export default function Updater() { ...@@ -7,17 +7,27 @@ export default function Updater() {
const { library, chainId } = useWeb3React() const { library, chainId } = useWeb3React()
const dispatch = useDispatch() const dispatch = useDispatch()
const [maxBlockNumber, setMaxBlockNumber] = useState<number | null>(null)
// because blocks arrive in bunches with longer polling periods, we just want
// to process the latest one.
const debouncedMaxBlockNumber = useDebounce<number | null>(maxBlockNumber, 100)
// update block number // update block number
useEffect(() => { useEffect(() => {
if (!library || !chainId) return if (!library || !chainId) return
const blockListener = (blockNumber: number) => { const blockListener = (blockNumber: number) => {
dispatch(updateBlockNumber({ chainId, blockNumber })) setMaxBlockNumber(maxBlockNumber => {
if (typeof maxBlockNumber !== 'number') return blockNumber
return Math.max(maxBlockNumber, blockNumber)
})
} }
setMaxBlockNumber(null)
library library
.getBlockNumber() .getBlockNumber()
.then(blockNumber => updateBlockNumber({ chainId, blockNumber })) .then(blockNumber => dispatch(updateBlockNumber({ chainId, blockNumber })))
.catch(error => console.error(`Failed to get block number for chainId ${chainId}`, error)) .catch(error => console.error(`Failed to get block number for chainId ${chainId}`, error))
library.on('block', blockListener) library.on('block', blockListener)
...@@ -26,5 +36,10 @@ export default function Updater() { ...@@ -26,5 +36,10 @@ export default function Updater() {
} }
}, [dispatch, chainId, library]) }, [dispatch, chainId, library])
useEffect(() => {
if (!chainId || !debouncedMaxBlockNumber) return
dispatch(updateBlockNumber({ chainId, blockNumber: debouncedMaxBlockNumber }))
}, [chainId, debouncedMaxBlockNumber, dispatch])
return null return null
} }
...@@ -18,7 +18,6 @@ export const addTransaction = createAction<{ ...@@ -18,7 +18,6 @@ export const addTransaction = createAction<{
approvalOfToken?: string approvalOfToken?: string
summary?: string summary?: string
}>('addTransaction') }>('addTransaction')
export const checkTransaction = createAction<{ chainId: number; hash: string; blockNumber: number }>('checkTransaction')
export const clearAllTransactions = createAction<{ chainId: number }>('clearAllTransactions') export const clearAllTransactions = createAction<{ chainId: number }>('clearAllTransactions')
export const finalizeTransaction = createAction<{ export const finalizeTransaction = createAction<{
chainId: number chainId: number
......
import { createReducer } from '@reduxjs/toolkit' import { createReducer } from '@reduxjs/toolkit'
import { import { addTransaction, clearAllTransactions, finalizeTransaction, SerializableTransactionReceipt } from './actions'
addTransaction,
checkTransaction,
clearAllTransactions,
finalizeTransaction,
SerializableTransactionReceipt
} from './actions'
const now = () => new Date().getTime() const now = () => new Date().getTime()
export interface TransactionDetails { export interface TransactionDetails {
hash: string hash: string
approvalOfToken?: string approvalOfToken?: string
blockNumberChecked?: number
summary?: string summary?: string
receipt?: SerializableTransactionReceipt receipt?: SerializableTransactionReceipt
addedTime: number addedTime: number
...@@ -44,13 +37,6 @@ export default createReducer(initialState, builder => ...@@ -44,13 +37,6 @@ export default createReducer(initialState, builder =>
if (!state[chainId]) return if (!state[chainId]) return
state[chainId] = {} state[chainId] = {}
}) })
.addCase(checkTransaction, (state, { payload: { chainId, blockNumber, hash } }) => {
if (!state[chainId]?.[hash]) {
throw Error('Attempted to check non-existent transaction.')
}
state[chainId][hash].blockNumberChecked = Math.max(blockNumber ?? 0, state[chainId][hash].blockNumberChecked ?? 0)
})
.addCase(finalizeTransaction, (state, { payload: { hash, chainId, receipt } }) => { .addCase(finalizeTransaction, (state, { payload: { hash, chainId, receipt } }) => {
if (!state[chainId]?.[hash]) { if (!state[chainId]?.[hash]) {
throw Error('Attempted to finalize non-existent transaction.') throw Error('Attempted to finalize non-existent transaction.')
......
...@@ -3,7 +3,7 @@ import { useDispatch, useSelector } from 'react-redux' ...@@ -3,7 +3,7 @@ import { useDispatch, useSelector } from 'react-redux'
import { useWeb3React } from '../../hooks' import { useWeb3React } from '../../hooks'
import { useAddPopup, useBlockNumber } from '../application/hooks' import { useAddPopup, useBlockNumber } from '../application/hooks'
import { AppDispatch, AppState } from '../index' import { AppDispatch, AppState } from '../index'
import { checkTransaction, finalizeTransaction } from './actions' import { finalizeTransaction } from './actions'
export default function Updater() { export default function Updater() {
const { chainId, library } = useWeb3React() const { chainId, library } = useWeb3React()
...@@ -19,24 +19,15 @@ export default function Updater() { ...@@ -19,24 +19,15 @@ export default function Updater() {
const addPopup = useAddPopup() const addPopup = useAddPopup()
useEffect(() => { useEffect(() => {
if (!chainId) return if (!chainId || !library || !lastBlockNumber) return
if (!library) return
if (!lastBlockNumber) return
Object.keys(allTransactions) Object.keys(allTransactions)
.filter(hash => !allTransactions[hash].receipt) .filter(hash => !allTransactions[hash].receipt)
.filter(hash => {
const lastChecked = allTransactions[hash].blockNumberChecked
return !lastChecked || lastChecked < lastBlockNumber
})
.forEach(hash => { .forEach(hash => {
library library
.getTransactionReceipt(hash) .getTransactionReceipt(hash)
.then(receipt => { .then(receipt => {
if (!receipt) { if (receipt) {
dispatch(checkTransaction({ chainId, hash, blockNumber: lastBlockNumber }))
} else {
dispatch( dispatch(
finalizeTransaction({ finalizeTransaction({
chainId, chainId,
......
...@@ -33,15 +33,19 @@ export function useETHBalances(uncheckedAddresses?: (string | undefined)[]): { [ ...@@ -33,15 +33,19 @@ export function useETHBalances(uncheckedAddresses?: (string | undefined)[]): { [
[uncheckedAddresses] [uncheckedAddresses]
) )
// used so that we do a deep comparison in `useEffect`
const serializedAddresses = JSON.stringify(addresses)
// add the listeners on mount, remove them on dismount // add the listeners on mount, remove them on dismount
useEffect(() => { useEffect(() => {
const addresses = JSON.parse(serializedAddresses)
if (addresses.length === 0) return if (addresses.length === 0) return
dispatch(startListeningForBalance({ addresses })) dispatch(startListeningForBalance({ addresses }))
return () => { return () => {
dispatch(stopListeningForBalance({ addresses })) dispatch(stopListeningForBalance({ addresses }))
} }
}, [addresses, dispatch]) }, [serializedAddresses, dispatch])
const rawBalanceMap = useSelector<AppState, AppState['wallet']['balances']>(({ wallet: { balances } }) => balances) const rawBalanceMap = useSelector<AppState, AppState['wallet']['balances']>(({ wallet: { balances } }) => balances)
...@@ -72,19 +76,29 @@ export function useTokenBalances( ...@@ -72,19 +76,29 @@ export function useTokenBalances(
() => tokens?.filter((t?: Token): t is Token => isAddress(t?.address) !== false) ?? [], () => tokens?.filter((t?: Token): t is Token => isAddress(t?.address) !== false) ?? [],
[tokens] [tokens]
) )
const tokenAddresses: string[] = useMemo(() => validTokens.map(t => t.address).sort(), [validTokens])
// used so that we do a deep comparison in `useEffect`
const serializedCombos: string = useMemo(() => {
return JSON.stringify(
!address || validTokens.length === 0
? []
: validTokens
.map(t => t.address)
.sort()
.map(tokenAddress => ({ address, tokenAddress }))
)
}, [address, validTokens])
// keep the listeners up to date // keep the listeners up to date
useEffect(() => { useEffect(() => {
if (!address) return const combos: TokenBalanceListenerKey[] = JSON.parse(serializedCombos)
if (tokenAddresses.length === 0) return if (combos.length === 0) return
const combos: TokenBalanceListenerKey[] = tokenAddresses.map(tokenAddress => ({ address, tokenAddress }))
dispatch(startListeningForTokenBalances(combos)) dispatch(startListeningForTokenBalances(combos))
return () => { return () => {
dispatch(stopListeningForTokenBalances(combos)) dispatch(stopListeningForTokenBalances(combos))
} }
}, [address, tokenAddresses, dispatch]) }, [address, serializedCombos, dispatch])
const rawBalanceMap = useSelector<AppState, AppState['wallet']['balances']>(({ wallet: { balances } }) => balances) const rawBalanceMap = useSelector<AppState, AppState['wallet']['balances']>(({ wallet: { balances } }) => balances)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment