From fd9110f432a969e36d71c5e72b66ddca5ee486ae Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 14 Jan 2021 18:15:29 +0100 Subject: [PATCH] Swap parachain to multi calls (w/ useCallMulti) (#4414) --- .../src/Overview/Parachain.tsx | 79 +++++++++++-------- packages/react-hooks/src/index.ts | 1 + packages/react-hooks/src/useCall.ts | 9 ++- packages/react-hooks/src/useCallMulti.ts | 76 ++++++++++++++++++ 4 files changed, 127 insertions(+), 38 deletions(-) create mode 100644 packages/react-hooks/src/useCallMulti.ts diff --git a/packages/page-parachains/src/Overview/Parachain.tsx b/packages/page-parachains/src/Overview/Parachain.tsx index 414fb370a0..da4fc5ae25 100644 --- a/packages/page-parachains/src/Overview/Parachain.tsx +++ b/packages/page-parachains/src/Overview/Parachain.tsx @@ -2,14 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 import type { LinkOption } from '@polkadot/apps-config/settings/types'; -import type { Option } from '@polkadot/types'; -import type { Balance, BlockNumber, HeadData, Header, ParaId, RelayChainBlockNumber } from '@polkadot/types/interfaces'; -import type { Codec, ITuple } from '@polkadot/types/types'; +import type { Option, Vec } from '@polkadot/types'; +import type { Balance, BlockNumber, HeadData, Header, ParaId } from '@polkadot/types/interfaces'; +import type { Codec } from '@polkadot/types/types'; import BN from 'bn.js'; import React, { useMemo } from 'react'; -import { useApi, useCall, useParaApi } from '@polkadot/react-hooks'; +import { useApi, useCall, useCallMulti, useParaApi } from '@polkadot/react-hooks'; import { BlockToTime, FormatBalance } from '@polkadot/react-query'; import { formatNumber } from '@polkadot/util'; @@ -22,31 +22,40 @@ interface Props { lastInclusion?: [string, string]; } -const transformDmpUmp = { - transform: (list: Codec[][]) => list.length -}; +type QueryResult = [Option, Option, Vec, Vec, Vec, Vec]; -const transformHead = { - transform: (headData: Option): string | null => - headData.isSome - ? sliceHex(headData.unwrap(), 18) - : null -}; - -// const transformMark = { -// transform: (watermark: Option): BlockNumber | null => -// watermark.isSome -// ? watermark.unwrap() -// : null -// }; +interface QueryState { + headHex: string | null; + updateAt: BlockNumber | null; + qDmp: number; + qUmp: number; + qHrmpE: number; + qHrmpI: number; +} const transformLast = { transform: (header: Header) => header.number.unwrap() }; -const transformUpgrade = { - transform: (opt: Option>): RelayChainBlockNumber | null => - opt.unwrapOr([null])[0] +const transformMulti = { + defaultValue: { + headHex: null, + qDmp: 0, + qHrmpE: 0, + qHrmpI: 0, + qUmp: 0, + updateAt: null + }, + transform: ([headData, optUp, dmp, ump, hrmpE, hrmpI]: QueryResult): QueryState => ({ + headHex: headData.isSome + ? sliceHex(headData.unwrap(), 18) + : null, + qDmp: dmp.length, + qHrmpE: hrmpE.length, + qHrmpI: hrmpI.length, + qUmp: ump.length, + updateAt: optUp.unwrapOr(null) + }) }; function getChainLink (endpoints: LinkOption[]): React.ReactNode { @@ -64,13 +73,15 @@ function Parachain ({ bestNumber, className = '', id, lastInclusion }: Props): R const { api: paraApi, endpoints } = useParaApi(id); const paraBest = useCall(paraApi?.derive.chain.bestNumber); const paraIssu = useCall(paraApi?.query.balances?.totalIssuance); - const headHex = useCall(api.query.paras.heads, [id], transformHead); - const updateAt = useCall(api.query.paras.futureCodeUpgrades, [id], transformUpgrade); - const qDmp = useCall(api.query.dmp.downwardMessageQueues, [id], transformDmpUmp); - const qUmp = useCall(api.query.dmp.relayDispatchQueues, [id], transformDmpUmp); - const qHrmpE = useCall(api.query.hrmp.hrmpEgressChannelsIndex, [id], transformDmpUmp); - const qHrmpI = useCall(api.query.hrmp.hrmpIngressChannelsIndex, [id], transformDmpUmp); const lastRelayNumber = useCall(lastInclusion && api.rpc.chain.getHeader, [lastInclusion && lastInclusion[1]], transformLast); + const paraInfo = useCallMulti([ + [api.query.paras.heads, id], + [api.query.paras.futureCodeUpgrades, id], + [api.query.dmp.downwardMessageQueues, id], + [api.query.ump.relayDispatchQueues, id], + [api.query.hrmp.hrmpEgressChannelsIndex, id], + [api.query.hrmp.hrmpIngressChannelsIndex, id] + ], transformMulti) as QueryState; // we have a default value, cast is safe const blockDelay = useMemo( () => lastRelayNumber && bestNumber && bestNumber.sub(lastRelayNumber).subn(1), @@ -86,7 +97,7 @@ function Parachain ({ bestNumber, className = '', id, lastInclusion }: Props): R

{formatNumber(id)}

{chainLink} - {headHex} + {paraInfo.headHex} {blockDelay && } {lastInclusion && lastRelayNumber && ( @@ -97,15 +108,15 @@ function Parachain ({ bestNumber, className = '', id, lastInclusion }: Props): R {paraBest && <>{formatNumber(paraBest)}} {paraIssu && } - {updateAt && bestNumber && ( + {paraInfo.updateAt && bestNumber && ( <> - - #{formatNumber(updateAt)} + + #{formatNumber(paraInfo.updateAt)} )} - {formatNumber(qUmp)} / {formatNumber(qDmp)} / {formatNumber(qHrmpE)} / {formatNumber(qHrmpI)} + {formatNumber(paraInfo.qUmp)} / {formatNumber(paraInfo.qDmp)} / {formatNumber(paraInfo.qHrmpE)} / {formatNumber(paraInfo.qHrmpI)} ); diff --git a/packages/react-hooks/src/index.ts b/packages/react-hooks/src/index.ts index 9ae55afcb5..b6323b0d7c 100644 --- a/packages/react-hooks/src/index.ts +++ b/packages/react-hooks/src/index.ts @@ -11,6 +11,7 @@ export { useBlockTime } from './useBlockTime'; export { useBlocksPerDays } from './useBlocksPerDays'; export { useCacheKey } from './useCacheKey'; export { useCall } from './useCall'; +export { useCallMulti } from './useCallMulti'; export { useContracts } from './useContracts'; export { useFormField } from './useFormField'; export { useDebounce } from './useDebounce'; diff --git a/packages/react-hooks/src/useCall.ts b/packages/react-hooks/src/useCall.ts index 315f2adbad..70f0d74872 100644 --- a/packages/react-hooks/src/useCall.ts +++ b/packages/react-hooks/src/useCall.ts @@ -4,12 +4,13 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars import type { Codec } from '@polkadot/types/types'; import type { CallOptions, CallParam, CallParams } from './types'; +import type { MountedRef } from './useIsMountedRef'; import { useEffect, useRef, useState } from 'react'; import { isNull, isUndefined } from '@polkadot/util'; -import { MountedRef, useIsMountedRef } from './useIsMountedRef'; +import { useIsMountedRef } from './useIsMountedRef'; type VoidFn = () => void; @@ -28,7 +29,7 @@ interface TrackFn { }; } -interface Tracker { +export interface Tracker { isActive: boolean; serialized: string | null; subscriber: TrackFnResult | null; @@ -39,7 +40,7 @@ interface TrackerRef { } // the default transform, just returns what we have -function transformIdentity (value: unknown): T { +export function transformIdentity (value: unknown): T { return value as T; } @@ -54,7 +55,7 @@ function extractParams (fn: unknown, params: unknown[], { paramMap = transfo } // unsubscribe and remove from the tracker -function unsubscribe (tracker: TrackerRef): void { +export function unsubscribe (tracker: TrackerRef): void { tracker.current.isActive = false; if (tracker.current.subscriber) { diff --git a/packages/react-hooks/src/useCallMulti.ts b/packages/react-hooks/src/useCallMulti.ts new file mode 100644 index 0000000000..7003e0e414 --- /dev/null +++ b/packages/react-hooks/src/useCallMulti.ts @@ -0,0 +1,76 @@ +// Copyright 2017-2021 @polkadot/react-hooks authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { ApiPromise } from '@polkadot/api'; +import type { QueryableStorageMultiArg } from '@polkadot/api/types'; +import type { Tracker } from './useCall'; +import type { MountedRef } from './useIsMountedRef'; + +import { useEffect, useRef, useState } from 'react'; + +import { useApi } from './useApi'; +import { transformIdentity, unsubscribe } from './useCall'; +import { useIsMountedRef } from './useIsMountedRef'; + +interface TrackerRef { + current: Tracker; +} + +interface CallOptions { + defaultValue?: T; + transform?: (value: any) => T; +} + +// subscribe, trying to play nice with the browser threads +function subscribe (api: ApiPromise, mountedRef: MountedRef, tracker: TrackerRef, calls: QueryableStorageMultiArg<'promise'>[], setValue: (value: T) => void, { transform = transformIdentity }: CallOptions = {}): void { + unsubscribe(tracker); + + setTimeout((): void => { + if (mountedRef.current) { + if (calls.length) { + // swap to acive mode + tracker.current.isActive = true; + + tracker.current.subscriber = api.queryMulti(calls, (value): void => { + // we use the isActive flag here since .subscriber may not be set on immediate callback) + if (mountedRef.current && tracker.current.isActive) { + mountedRef.current && tracker.current.isActive && setValue( + transform(value) + ); + } + }); + } else { + tracker.current.subscriber = null; + } + } + }, 0); +} + +// very much copied from useCall +export function useCallMulti (calls?: QueryableStorageMultiArg<'promise'>[] | null | false, options?: CallOptions): T | undefined { + const { api } = useApi(); + const mountedRef = useIsMountedRef(); + const tracker = useRef({ isActive: false, serialized: null, subscriber: null }); + const [value, setValue] = useState((options || {}).defaultValue); + + // initial effect, we need an un-subscription + useEffect((): () => void => { + return () => unsubscribe(tracker); + }, []); + + // on changes, re-subscribe + useEffect((): void => { + // check if we have a function & that we are mounted + if (mountedRef.current && calls) { + const serialized = JSON.stringify(calls); + + if (serialized !== tracker.current.serialized) { + tracker.current.serialized = serialized; + + subscribe(api, mountedRef, tracker, calls, setValue, options); + } + } + }, [api, calls, options, mountedRef]); + + return value; +}