react-use#usePrevious TypeScript Examples
The following examples show how to use
react-use#usePrevious.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: useCacheRender.ts From UUI with MIT License | 6 votes |
export function useValueCacheRender<T>(data: T, render: (data: T) => React.ReactNode, options: { comparator?: (previous: T, current: T) => boolean }) {
const [rendered, setRendered] = useState(render(data))
const previous = usePrevious<T>(data) as T
const current = data
useEffect(() => {
const isSame = options?.comparator ? options.comparator(previous, current) : isEqual(previous, current)
if (!isSame) {
setRendered(render(current))
}
}, [previous, current, options, render])
return rendered
}
Example #2
Source File: TabsOfTruth.tsx From mStable-apps with GNU Lesser General Public License v3.0 | 6 votes |
TabsOfTruth: FC<
State & {
setActiveIndex: (index: number) => void
className?: string
}
> = ({ tabs, setActiveIndex, activeTabIndex, className }) => {
const container = useRef<HTMLDivElement>(undefined as never)
const prevActiveTabIndex = usePrevious(activeTabIndex)
const [activePos, setActivePos] = useState<[number, number]>([0, 0])
useLayoutEffect(() => {
const { offsetWidth, offsetLeft } = container.current.childNodes.item(activeTabIndex + 1) as HTMLElement
setActivePos([offsetLeft, offsetWidth])
}, [activeTabIndex])
return (
<TabsContainer ref={container} className={className}>
<Transition in={activeTabIndex !== prevActiveTabIndex} appear timeout={250} unmountOnExit={false}>
{className => <ActiveTab pos={activePos} className={className} />}
</Transition>
{tabs.map(({ id, title, active }, index) => (
<TabButton
active={active}
key={id}
onClick={() => {
setActiveIndex(index)
}}
>
{title}
</TabButton>
))}
</TabsContainer>
)
}
Example #3
Source File: useCacheRender.ts From UUI with MIT License | 5 votes |
export function useArrayCacheRender<T>(
data: T[],
render: (data: T) => React.ReactNode,
options: {
id: (i: T) => string;
comparator?: (previous: T, current: T) => boolean;
},
) {
const [list, setList] = useState<{
id: string;
rendered: React.ReactNode;
}[]>([])
const previous = usePrevious<T[]>(data) as T[]
const current = data
useEffect(() => {
const isSameOne = (i: T, j: T) => options.id(i) === options.id(j)
const intersected = intersectionWith(previous, current, isSameOne)
const removing = xorWith(previous, intersected, isSameOne)
const adding = xorWith(current, intersected, isSameOne)
const updating = intersected.filter((i) => {
const p = previous.find((j) => options.id(i) === options.id(j))
const c = current.find((j) => options.id(i) === options.id(j))
if (!p) return false
if (!c) return false
return options.comparator ? options.comparator(c, p) : !isEqual(c, p)
})
const newList = clone(list)
for (const i of removing) {
remove(newList, (r) => r.id === options.id(i))
}
for (const i of updating) {
const index = list.findIndex((r) => r.id === options.id(i))
const c = current.find((c) => options.id(c) === options.id(i))
if (index > -1 && c) newList[index] = { id: options.id(c), rendered: render(c) }
}
for (const i of adding) {
newList.push({ id: options.id(i), rendered: render(i) })
}
setList(newList)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [previous, current])
const rendered = useMemo(() => {
return list.map((i) => i.rendered)
}, [list])
return rendered
}
Example #4
Source File: ClientConnectionSettingsForm.tsx From flood with GNU General Public License v3.0 | 5 votes |
ClientConnectionSettingsForm: FC<ClientConnectionSettingsFormProps> = ({
onSettingsChange,
}: ClientConnectionSettingsFormProps) => {
const {i18n} = useLingui();
const [selectedClient, setSelectedClient] = useState<ClientConnectionSettings['client']>(DEFAULT_SELECTION);
const prevSelectedClient = usePrevious(selectedClient);
if (selectedClient !== prevSelectedClient) {
onSettingsChange(null);
}
let settingsForm: ReactNode = null;
switch (selectedClient) {
case 'Deluge':
settingsForm = <DelugeConnectionSettingsForm onSettingsChange={onSettingsChange} />;
break;
case 'qBittorrent':
settingsForm = <QBittorrentConnectionSettingsForm onSettingsChange={onSettingsChange} />;
break;
case 'rTorrent':
settingsForm = <RTorrentConnectionSettingsForm onSettingsChange={onSettingsChange} />;
break;
case 'Transmission':
settingsForm = <TransmissionConnectionSettingsForm onSettingsChange={onSettingsChange} />;
break;
default:
break;
}
return (
<div>
<FormRow>
<Select
id="client"
label={i18n._('connection.settings.client.select')}
onSelect={(newSelectedClient) => {
setSelectedClient(newSelectedClient as ClientConnectionSettings['client']);
}}
defaultID={DEFAULT_SELECTION}
>
{SUPPORTED_CLIENTS.map((client) => (
<SelectItem key={client} id={client}>
<Trans id={`connection.settings.${client.toLowerCase()}`} />
</SelectItem>
))}
</Select>
</FormRow>
{settingsForm}
</div>
);
}
Example #5
Source File: AccountProvider.tsx From mStable-apps with GNU Lesser General Public License v3.0 | 5 votes |
OnboardConnection: FC = ({ children }) => {
const network = useNetwork()
const [chainId, setChainId] = useChainIdCtx()
const [injectedChainId] = useInjectedChainIdCtx()
const previousInjectedChainId = usePrevious(injectedChainId)
const jsonRpcProviders = useJsonRpcProviders()
const [injectedProvider] = useInjectedProviderCtx()
const [, setSigners] = useSignerCtx()
useEffect(() => {
if (chainId) localStorage.setItem('mostRecentChainId', chainId as unknown as string)
}, [chainId])
const injectedMismatching = injectedChainId !== chainId
useEffect(() => {
if (!chainId || !injectedChainId) return
// Change chainId when injectedChainId changes and doesn't match chainId
if (injectedMismatching && previousInjectedChainId) {
setChainId(injectedChainId)
}
}, [chainId, injectedChainId, injectedMismatching, previousInjectedChainId, setChainId])
useEffect(() => {
if (!injectedProvider || !previousInjectedChainId) return
const method = network.isMetaMaskDefault ? 'wallet_switchEthereumChain' : 'wallet_addEthereumChain'
const data = [
{
chainId: utils.hexStripZeros(utils.hexlify(network.chainId)),
...(!network.isMetaMaskDefault && {
chainName: `${network.protocolName} (${network.chainName})`,
nativeCurrency: network.nativeToken,
rpcUrls: network.rpcEndpoints,
blockExplorerUrls: [network.getExplorerUrl()],
}),
},
]
injectedProvider.send(method, data).catch(error => {
console.warn(error)
})
}, [injectedProvider, network, previousInjectedChainId])
useEffect(() => {
if (!jsonRpcProviders) return setSigners(undefined)
if (injectedMismatching) {
return setSigners({
provider: jsonRpcProviders.provider as never,
})
}
setSigners({
provider: injectedProvider ?? (jsonRpcProviders.provider as never),
signer: injectedProvider?.getSigner() as never,
})
}, [injectedMismatching, injectedProvider, jsonRpcProviders, setSigners])
// Remount Onboard when the chainId changes
// Necessitated by Onboard's design and internal state
return (
<OnboardProvider key={chainId} chainId={chainId ?? ChainIds.EthereumMainnet}>
{children}
</OnboardProvider>
)
}
Example #6
Source File: transactionsUpdater.ts From mStable-apps with GNU Lesser General Public License v3.0 | 5 votes |
TransactionsUpdater = (): null => {
const account = useAccount()
const accountPrev = usePrevious(account)
const provider = useSignerOrProvider()
const blockNumber = useBlockNow()
const state = useTransactionsState()
const { check, finalize, reset } = useTransactionsDispatch()
/**
* Reset transactions state on account change
*/
useEffect(() => {
if (accountPrev !== account) {
reset()
}
}, [account, accountPrev, reset])
/**
* Check pending transaction status on new blocks, and finalize if possible.
*/
useEffect(
(): (() => void) | void => {
if (provider && blockNumber) {
let stale = false
Object.values(state)
.filter(tx => STATUS_NEEDS_CHECK.includes(tx.status) && tx.hash && tx.blockNumber !== blockNumber)
.forEach(tx => {
;(((provider as Signer).provider || provider) as Provider)
.getTransactionReceipt(tx.hash as string)
.then((receipt: TransactionReceipt) => {
if (!stale) {
if (!receipt) {
if (tx?.manifest?.id) {
check(tx.manifest.id, blockNumber)
}
} else {
finalize(tx.manifest, receipt)
}
}
})
.catch(() => {
if (tx?.manifest?.id) {
check(tx.manifest.id, blockNumber)
}
})
})
return () => {
stale = true
}
}
return undefined
},
// `blockNumber` and `provider` should be the only deps; otherwise it will
// check too often.
// eslint-disable-next-line react-hooks/exhaustive-deps
[blockNumber, provider],
)
return null
}
Example #7
Source File: useBigDecimalInput.ts From mStable-apps with GNU Lesser General Public License v3.0 | 5 votes |
useBigDecimalInput = (
initialValue?: BigDecimal | string,
options?: number | { min?: number; max?: number; decimals?: number },
): [
BigDecimal | undefined,
string | undefined,
(formValue: (string | null) | (string | undefined)) => void,
(amount?: BigDecimal) => void,
] => {
const decimals = (typeof options === 'number' ? options : options?.decimals) ?? 18
const prevDecimals = usePrevious(decimals)
const [value, setValue] = useState<BigDecimal | undefined>(
initialValue instanceof BigDecimal ? initialValue : BigDecimal.maybeParse(initialValue, decimals),
)
const [formValue, setFormValue] = useState<string | undefined>(value?.simple !== 0 ? value?.string : undefined)
const onChange = useCallback(
_formValue => {
const amount = BigDecimal.maybeParse(_formValue, decimals)
if (
amount &&
typeof options === 'object' &&
((options.min && amount.simple < options.min) || (options.max && amount.simple > options.max))
) {
// Ignore inputs outside parameters
return
}
setFormValue(_formValue ?? undefined)
setValue(BigDecimal.maybeParse(_formValue, decimals))
},
[decimals, options],
)
useEffect(() => {
if (decimals !== prevDecimals) {
setValue(BigDecimal.maybeParse(formValue, decimals))
}
}, [decimals, formValue, prevDecimals])
return [value, formValue, onChange, setValue]
}
Example #8
Source File: Form.tsx From yugong with MIT License | 4 votes |
Form: React.FC<FormProps> = (props) => {
const {
registersFunction,
eventDispatch,
classes,
moduleId,
api
} = props;
const { setRunningTimes } = useDispatch<Dispatch>().runningTimes;
const [formColumns, setformColumns] = useState<AnyObjectType[]>([]);
const [resetBtn, setResetBtn] = useState<string>();
const [subBtn, setSubBtn] = useState<string>();
const [configArgs, setConfigArgs] = useState<[ArgumentsMixed, ArgumentsString, ArgumentsString]>()
// 数据编译到显示端
const formColumnsCompiler = useCallback(
(formColumns: AnyObjectType[]) => {
formColumns.forEach(column => {
Object.keys(column).forEach(key => {
let element = column[key];
if (isType(element, 'String')) {
getResult(element);
};
})
});
setformColumns(formColumns);
},
[],
)
const setForm = useCallback(
(...args: [ArgumentsMixed, ArgumentsString, ArgumentsString] ) => {
const [formColumns, resetText, submitText] = args;
setConfigArgs(args);
const columns = getArgumentsItem(formColumns);
const reset = getArgumentsItem(resetText);
setResetBtn(reset as string)
const submit = getArgumentsItem(submitText);
setSubBtn(submit as string)
if (isType(columns, "Array")) {
formColumnsCompiler(columns as AnyObjectType[])
} else {
message.error('表单数据不正确')
}
},
[formColumnsCompiler],
)
const resetForm = useCallback(
() => {
if (configArgs) {
const [formColumns, resetText, submitText] = configArgs;
setForm(formColumns, resetText, submitText);
}
},
[configArgs, setForm],
)
const prevFormColumns = usePrevious<any>(formColumns);
// First setup registers
useEffect(() => {
registersFunction({
setForm,
resetForm
})
}, [setForm, registersFunction, resetForm])
// Second, distributing events
useEffect(() => {
eventDispatch().mount()
return () => {
eventDispatch().unmount();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const onSubmit = useCallback(
async (values) => {
console.log('values', values);
setRunningTimes({
[`form_${moduleId}`]: values
});
const apiArguments = api?.find((item) => item.apiId === 'submit');
if (apiArguments) {
apiArguments.body = [{ type: 'mixed', fieldName: 'formdata', data: values }];
await requester(apiArguments || {});
}
eventDispatch().submit();
},
[api, eventDispatch, moduleId, setRunningTimes],
)
// 得到一个初始值
useEffect(() => {
const formColumns = config.exposeFunctions![0].arguments![0] as ArgumentsMixed;
const resetText = config.exposeFunctions![0].arguments![1] as ArgumentsString;
const submitText = config.exposeFunctions![0].arguments![2] as ArgumentsString;
setForm(formColumns, resetText, submitText);
}, [setForm])
// 数据比较更新
const [updateKey, setUpdateKey] = useState(Date.now());
useEffect(() => {
formColumns.some((item: SubItemValue, index) => {
if (item.initialValue === prevFormColumns?.[index]?.initialValue) {
setUpdateKey(Date.now())
return true
}
return false
})
}, [prevFormColumns, formColumns])
const handleOnFocus = useCallback(
e => {
if(e.target.parentNode.className.indexOf('ant-picker-input') >= 0 && isMobile){
e.target.setAttribute('readonly', true);
}
},
[],
)
return (
<Wrapper {...props} maxWidth>
<div className={s.wrap}>
<BetaSchemaForm<DataItem>
key={updateKey}
className={classNames(s.form, classes.form)}
shouldUpdate={false}
layoutType="Form"
onFinish={onSubmit}
columns={formColumns}
autoFocusFirstInput={false}
onFocus={handleOnFocus}
submitter={{
// 配置按钮文本
searchConfig: {
resetText: resetBtn || '重置',
submitText: subBtn || '提交',
},
submitButtonProps: {
className: classes.submit,
},
resetButtonProps: {
className: classes.reset
}
}}
/>
</div>
</Wrapper>
)
}
Example #9
Source File: useEstimatedOutput.ts From mStable-apps with GNU Lesser General Public License v3.0 | 4 votes |
useEstimatedOutput = (
inputValue?: BigDecimalInputValue,
outputValue?: BigDecimalInputValue,
lpPriceAdjustment?: LPPriceAdjustment,
shouldSkip?: boolean,
): Output => {
const inputValuePrev = usePrevious(inputValue)
const outputValuePrev = usePrevious(outputValue)
const [estimatedOutputRange, setEstimatedOutputRange] = useFetchState<[BigDecimal, BigDecimal]>()
const [action, setAction] = useState<Action | undefined>()
const signer = useSigner()
const massetState = useSelectedMassetState() as MassetState
const { address: massetAddress, fAssets, bAssets, feeRate: swapFeeRate, redemptionFeeRate } = massetState
const poolAddress = Object.values(fAssets).find(
f =>
f.feederPoolAddress === inputValue?.address ||
f.feederPoolAddress === outputValue?.address ||
f.address === inputValue?.address ||
f.address === outputValue?.address,
)?.feederPoolAddress
const inputRatios = useMassetInputRatios()
const scaledInput = useScaledInput(inputValue, outputValue, inputRatios)
const contract: Contract | undefined = useMemo(() => {
if (!signer) return
// use feeder pool to do swap
if (poolAddress) {
return FeederPool__factory.connect(poolAddress, signer)
}
return Masset__factory.connect(massetAddress, signer)
}, [poolAddress, massetAddress, signer])
const isFeederPool = contract?.address === poolAddress
const exchangeRate = useMemo<FetchState<number>>(() => {
if (shouldSkip) return {}
if (estimatedOutputRange.fetching) return { fetching: true }
if (!scaledInput?.high || !outputValue || !estimatedOutputRange.value) return {}
const [, high] = estimatedOutputRange.value
if (!high.exact.gt(0) || !scaledInput.high.exact.gt(0)) {
return { error: 'Amount must be greater than zero' }
}
const value = high.simple / scaledInput.high.simple
return { value }
}, [estimatedOutputRange, scaledInput, outputValue, shouldSkip])
const feeRate = useMemo<FetchState<BigDecimal>>(() => {
if (shouldSkip || !withFee.has(action) || !estimatedOutputRange.value?.[1]) return {}
if (estimatedOutputRange.fetching) return { fetching: true }
const _feeRate = action === Action.SWAP ? swapFeeRate : redemptionFeeRate
const swapFee = estimatedOutputRange.value[1]
.scale()
.divPrecisely(BigDecimal.ONE.sub(_feeRate))
.sub(estimatedOutputRange.value[1].scale())
return { value: swapFee }
}, [action, estimatedOutputRange, swapFeeRate, redemptionFeeRate, shouldSkip])
const priceImpact = useMemo<FetchState<PriceImpact>>(() => {
if (estimatedOutputRange.fetching || !estimatedOutputRange.value) return { fetching: true }
if (!scaledInput || !scaledInput.high.exact.gt(0)) return {}
const value = getPriceImpact([scaledInput.scaledLow, scaledInput.scaledHigh], estimatedOutputRange.value, lpPriceAdjustment)
return { value }
}, [estimatedOutputRange.fetching, estimatedOutputRange.value, lpPriceAdjustment, scaledInput])
/*
* |------------------------------------------------------|
* | ROUTES |
* | -----------------------------------------------------|
* | Input | Output | Function | Tokens |
* | -----------------------------------------------------|
* | basset | masset | masset mint | 1 basset, 1 masset |
* | masset | basset | masset redeem | 1 masset, 1 basset |
* | basset | basset | masset swap | 2 bassets |
* | fasset | basset | fpool swap | 1 fasset |
* | fasset | masset | fpool swap | 1 fasset |
* |------------------------------------------------------|
*/
const inputEq = inputValuesAreEqual(inputValue, inputValuePrev)
const outputEq = inputValuesAreEqual(outputValue, outputValuePrev)
const eq = inputEq && outputEq
const [update] = useDebounce(
() => {
if (!scaledInput || !outputValue || shouldSkip || !contract) return
const { address: inputAddress, decimals: inputDecimals } = inputValue
const { address: outputAddress, decimals: outputDecimals } = outputValue
const isLPRedeem = contract.address === inputAddress
const isLPMint = contract.address === outputAddress
const isMassetMint = bAssets[inputAddress]?.address && outputAddress === massetAddress
const isBassetSwap = [inputAddress, outputAddress].filter(address => bAssets[address]?.address).length === 2
const isInvalid = inputAddress === outputAddress
if (!scaledInput.high.exact.gt(0)) return
// same -> same; fallback to input value 1:1
if (isInvalid) {
setEstimatedOutputRange.value([scaledInput.scaledLow, scaledInput.high])
return
}
let outputLowPromise: Promise<BigNumber> | undefined
let outputHighPromise: Promise<BigNumber> | undefined
if (isMassetMint || isLPMint) {
setAction(Action.MINT)
outputLowPromise = contract.getMintOutput(inputAddress, scaledInput.low.scale(inputDecimals).exact)
outputHighPromise = contract.getMintOutput(inputAddress, scaledInput.high.exact)
} else if ((isFeederPool || isBassetSwap) && !isLPRedeem) {
setAction(Action.SWAP)
outputLowPromise = contract.getSwapOutput(inputAddress, outputAddress, scaledInput.low.scale(inputDecimals).exact)
outputHighPromise = contract.getSwapOutput(inputAddress, outputAddress, scaledInput.high.exact)
} else if (!isFeederPool || isLPRedeem) {
setAction(Action.REDEEM)
outputLowPromise = contract.getRedeemOutput(outputAddress, scaledInput.low.scale(inputDecimals).exact)
outputHighPromise = contract.getRedeemOutput(outputAddress, scaledInput.high.exact)
}
if (outputLowPromise && outputHighPromise) {
setEstimatedOutputRange.fetching()
Promise.all([outputLowPromise, outputHighPromise])
.then(data => {
const [_low, _high] = data
const low = new BigDecimal(_low, outputDecimals)
const high = new BigDecimal(_high, outputDecimals)
setEstimatedOutputRange.value([low, high])
})
.catch(_error => {
setEstimatedOutputRange.error(sanitizeMassetError(_error))
})
return
}
setEstimatedOutputRange.value()
},
2500,
[eq],
)
useEffect(() => {
if (shouldSkip) return
if (!eq && contract && scaledInput && outputValue) {
if (scaledInput.high.exact.gt(0)) {
setEstimatedOutputRange.fetching()
update()
} else {
setEstimatedOutputRange.value()
}
}
}, [eq, contract, setEstimatedOutputRange, update, scaledInput, outputValue, shouldSkip])
return useMemo(
() => ({
estimatedOutputAmount: {
fetching: estimatedOutputRange.fetching,
error: estimatedOutputRange.error,
value: estimatedOutputRange.value?.[1],
},
priceImpact,
exchangeRate,
feeRate,
}),
[estimatedOutputRange, priceImpact, exchangeRate, feeRate],
)
}
Example #10
Source File: ApolloProvider.tsx From mStable-apps with GNU Lesser General Public License v3.0 | 4 votes |
ApolloProvider: FC = ({ children }) => {
const addErrorNotification = useAddErrorNotification()
const [persisted, setPersisted] = useState(false)
const network = useNetwork()
const previousChainId = usePrevious(network.chainId)
const networkChanged = previousChainId && network.chainId !== previousChainId
// Serialized array of failed endpoints to be excluded from the client
const [failedEndpoints] = useState<string>('')
const handleError = useCallback(
(message: string, error?: unknown): void => {
console.error(message, error)
// Not significant at the moment; falls back to the hosted service
if (message.includes('Exhausted list of indexers')) return
let sanitizedError: string = message
let body: string | undefined
if (message.includes('Failed to query subgraph deployment')) {
sanitizedError = `Subgraph: ${message.split(': ')[1] ?? message}`
}
if ((error as { operation?: Operation })?.operation?.operationName) {
body = `Subgraph: ${(error as { operation: Operation }).operation.operationName}`
}
addErrorNotification(sanitizedError, body)
},
[addErrorNotification],
)
useEffect(() => {
Promise.all(
Object.keys(caches).map(clientName =>
persistCache({
cache: caches[clientName as keyof ApolloClients] as never,
storage: window.localStorage as never,
key: `${network.chainId}-${clientName}`,
}),
),
)
.catch(_error => {
console.warn('Cache persist error', _error)
})
.finally(() => {
setPersisted(true)
})
}, [setPersisted, network.chainId])
const apollo = useMemo<{ ready: true; clients: ApolloClients } | { ready: false }>(() => {
if (!persisted) return { ready: false }
// const _failedEndpoints = failedEndpoints.split(',')
const errorLink = onError(error => {
const { networkError, graphQLErrors } = error
if (graphQLErrors) {
graphQLErrors.forEach(({ message, ..._error }) => {
// if (_failedEndpoints.includes(ctx.uri)) return
handleError(message, error)
// On any GraphQL error, mark the endpoint as failed; this may be
// excessive, but failed endpoints are merely deprioritised rather than
// excluded completely.
// _failedEndpoints.push(ctx.uri)
})
}
if (networkError) {
handleError(networkError.message, error)
}
// setFailedEndpoints(_failedEndpoints.join(','))
})
const retryIf = (error: { statusCode: number }) => {
const doNotRetryCodes = [500, 400]
return !!error && !doNotRetryCodes.includes(error.statusCode)
}
const clients = (Object.keys(caches) as AllGqlEndpoints[])
.map<[AllGqlEndpoints, ApolloClient<NormalizedCacheObject>]>(name => {
if (!Object.prototype.hasOwnProperty.call(network.gqlEndpoints, name)) {
return [name, dummyClient]
}
const endpoints = network.gqlEndpoints[name as keyof typeof network['gqlEndpoints']]
const preferred = endpoints.filter(endpoint => !failedEndpoints.split(',').includes(endpoint))[0]
const fallback = endpoints[0] // There is always a fallback, even if it failed
const endpoint = preferred ?? fallback
const timeoutLink = new ApolloLinkTimeout(30000)
const endpointNameLink = new ApolloLink((operation, forward) => {
operation.extensions.endpointName = name
return forward(operation)
})
const httpLink = new HttpLink({ uri: endpoint })
const retryLink = new RetryLink({ delay: { initial: 1e3, max: 5e3, jitter: true }, attempts: { max: 1, retryIf } })
const link = ApolloLink.from([endpointNameLink, networkStatusLink, retryLink, timeoutLink, errorLink, httpLink])
const client = new ApolloClient<NormalizedCacheObject>({
cache: caches[name],
link,
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network',
errorPolicy: 'all',
},
query: {
fetchPolicy: 'cache-first',
errorPolicy: 'all',
},
},
})
return [name, client]
})
.reduce<ApolloClients>(
(prev, [clientName, client]) => ({ ...prev, [clientName as keyof ApolloClients]: client }),
{} as ApolloClients,
)
return { ready: true, clients }
}, [persisted, failedEndpoints, handleError, network])
useEffect(() => {
// Reset caches that can have conflicting keyFields on network change
// This prevents cached data from a previously selected network being used
// on a newly-selected network
if (networkChanged && (apollo as { clients: ApolloClients }).clients) {
;(apollo as { clients: ApolloClients }).clients.blocks.resetStore().catch(error => {
console.error(error)
})
}
}, [apollo, networkChanged])
return apollo.ready ? <apolloClientsCtx.Provider value={apollo.clients}>{children}</apolloClientsCtx.Provider> : <Skeleton />
}
Example #11
Source File: tokenSubscriptionsUpdater.ts From mStable-apps with GNU Lesser General Public License v3.0 | 4 votes |
TokenSubscriptionsUpdater = (): null => {
const { reset, updateBalances, updateAllowances } = useTokensDispatch()
const signer = useSigner()
const [chainId] = useChainIdCtx()
const prevChainId = usePrevious(chainId)
const account = useAccount()
const prevAccount = usePrevious(account)
const blockNumber = useBlockNow()
const balanceSubscriptionsSerialized = useBalanceSubscriptionsSerialized()
const allowanceSubscriptionsSerialized = useAllowanceSubscriptionsSerialized()
// Clear all contracts and tokens if the account/chain changes.
useEffect(() => {
if (prevAccount !== account || chainId !== prevChainId) {
reset()
}
}, [account, chainId, prevAccount, prevChainId, reset])
useEffect(() => {
if (!account || !signer || !signer.provider || chainId !== prevChainId) return
const allowanceSubs: {
address: string
spenders: string[]
decimals: number
}[] = JSON.parse(allowanceSubscriptionsSerialized)
const allowancePromises = allowanceSubs.flatMap(({ address, spenders, decimals }) =>
spenders.map(async spender => {
const data = await (signer.provider as Provider).call({
to: address,
data: contractInterface.encodeFunctionData('allowance', [account, spender]),
})
const allowance = contractInterface.decodeFunctionResult('allowance', data)
return {
address,
spender,
allowance: new BigDecimal(allowance[0], decimals),
}
}),
)
Promise.all(allowancePromises)
.then(allowances => {
updateAllowances(
allowances.reduce<Parameters<typeof updateAllowances>[0]>(
(_allowances, { address, allowance, spender }) => ({
..._allowances,
[address]: { ..._allowances[address], [spender]: allowance },
}),
{},
),
)
})
.catch(console.error)
}, [account, allowanceSubscriptionsSerialized, blockNumber, chainId, prevChainId, signer, updateAllowances])
useEffect(() => {
if (!account || !signer || !signer.provider || chainId !== prevChainId) return
const balanceSubs: { address: string; decimals: number }[] = JSON.parse(balanceSubscriptionsSerialized)
const balancePromises = balanceSubs
.filter(({ address }) => address !== constants.AddressZero)
.map(async ({ address, decimals }) => {
const data = await (signer.provider as Provider).call({
to: address,
data: contractInterface.encodeFunctionData('balanceOf', [account]),
})
const balance = contractInterface.decodeFunctionResult('balanceOf', data)
return [address, new BigDecimal(balance[0], decimals)]
})
Promise.all(balancePromises)
.then(balances => {
updateBalances(Object.fromEntries(balances))
})
.catch(console.error)
}, [account, balanceSubscriptionsSerialized, blockNumber, chainId, prevChainId, signer, updateBalances])
useEffect(() => {
if (account && signer?.provider) {
signer.provider.getBalance(account).then(_balance => {
updateBalances({
[constants.AddressZero as string]: new BigDecimal(_balance),
})
})
} else {
updateBalances({
[constants.AddressZero as string]: BigDecimal.ZERO,
})
}
}, [blockNumber, account, signer, updateBalances])
return null
}