diff --git a/CHANGELOG.md b/CHANGELOG.md index 4069d8045e..612eb79a4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Changes: - Adjust teleport to only cater for latest XCM - Add blocktime latency tab to Explorer +- Allow decoded extrinsics to populate submission ## 0.106.1 Feb 14, 2022 diff --git a/packages/page-extrinsics/src/Decoder.tsx b/packages/page-extrinsics/src/Decoder.tsx index 9e7bdc01df..ff9e0b59ce 100644 --- a/packages/page-extrinsics/src/Decoder.tsx +++ b/packages/page-extrinsics/src/Decoder.tsx @@ -3,6 +3,7 @@ import type { SubmittableExtrinsic, SubmittableExtrinsicFunction } from '@polkadot/api/types'; import type { Call } from '@polkadot/types/interfaces'; +import type { DecodedExtrinsic } from './types'; import React, { useCallback, useState } from 'react'; import styled from 'styled-components'; @@ -17,6 +18,7 @@ import { useTranslation } from './translate'; interface Props { className?: string; + setLast: (value: DecodedExtrinsic | null) => void; } interface ExtrinsicInfo { @@ -37,7 +39,7 @@ const DEFAULT_INFO: ExtrinsicInfo = { extrinsicHex: null }; -function Decoder ({ className }: Props): React.ReactElement { +function Decoder ({ className, setLast }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); const [{ decoded, extrinsic, extrinsicCall, extrinsicError, extrinsicFn }, setExtrinsicInfo] = useState(DEFAULT_INFO); @@ -68,11 +70,13 @@ function Decoder ({ className }: Props): React.ReactElement { } setExtrinsicInfo({ ...DEFAULT_INFO, decoded, extrinsic, extrinsicCall, extrinsicFn, extrinsicHex }); + setLast({ call: extrinsicCall, fn: extrinsicFn }); } catch (e) { setExtrinsicInfo({ ...DEFAULT_INFO, extrinsicError: (e as Error).message }); + setLast(null); } }, - [api] + [api, setLast] ); return ( diff --git a/packages/page-extrinsics/src/Submission.tsx b/packages/page-extrinsics/src/Submission.tsx index 011f1fb8a7..ee63b0e876 100644 --- a/packages/page-extrinsics/src/Submission.tsx +++ b/packages/page-extrinsics/src/Submission.tsx @@ -1,7 +1,9 @@ // Copyright 2017-2022 @polkadot/app-extrinsics authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { SubmittableExtrinsic } from '@polkadot/api/types'; +import type { SubmittableExtrinsic, SubmittableExtrinsicFunction } from '@polkadot/api/types'; +import type { RawParam } from '@polkadot/react-params/types'; +import type { DecodedExtrinsic } from './types'; import React, { useCallback, useState } from 'react'; @@ -14,22 +16,45 @@ import { useTranslation } from './translate'; interface Props { className?: string; + defaultValue: DecodedExtrinsic | null; } -function Selection ({ className }: Props): React.ReactElement { +interface DefaultExtrinsic { + defaultArgs?: RawParam[]; + defaultFn: SubmittableExtrinsicFunction<'promise'>; +} + +function extractDefaults (value: DecodedExtrinsic | null, defaultFn: SubmittableExtrinsicFunction<'promise'>): DefaultExtrinsic { + if (!value) { + return { defaultFn }; + } + + return { + defaultArgs: value.call.args.map((value) => ({ + isValid: true, + value + })), + defaultFn: value.fn + }; +} + +function Selection ({ className, defaultValue }: Props): React.ReactElement { const { t } = useTranslation(); const { apiDefaultTxSudo } = useApi(); const [accountId, setAccountId] = useState(null); const [error, setError] = useState(null); const [extrinsic, setExtrinsic] = useState | null>(null); + const [{ defaultArgs, defaultFn }] = useState(() => extractDefaults(defaultValue, apiDefaultTxSudo)); const _onExtrinsicChange = useCallback( - (method?: SubmittableExtrinsic<'promise'>) => setExtrinsic(() => method || null), + (method?: SubmittableExtrinsic<'promise'>) => + setExtrinsic(() => method || null), [] ); const _onExtrinsicError = useCallback( - (error?: Error | null) => setError(error ? error.message : null), + (error?: Error | null) => + setError(error ? error.message : null), [] ); @@ -47,7 +72,8 @@ function Selection ({ className }: Props): React.ReactElement { type='account' /> ('submit the following extrinsic')} onChange={_onExtrinsicChange} onError={_onExtrinsicError} diff --git a/packages/page-extrinsics/src/index.tsx b/packages/page-extrinsics/src/index.tsx index 5e0d115e52..2eefc1c441 100644 --- a/packages/page-extrinsics/src/index.tsx +++ b/packages/page-extrinsics/src/index.tsx @@ -2,8 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 import type { AppProps as Props } from '@polkadot/react-components/types'; +import type { DecodedExtrinsic } from './types'; -import React, { useRef } from 'react'; +import React, { useRef, useState } from 'react'; import { Route, Switch } from 'react-router'; import { Tabs } from '@polkadot/react-components'; @@ -14,6 +15,7 @@ import { useTranslation } from './translate'; function ExtrinsicsApp ({ basePath }: Props): React.ReactElement { const { t } = useTranslation(); + const [decoded, setDecoded] = useState(null); const itemsRef = useRef([ { @@ -34,9 +36,11 @@ function ExtrinsicsApp ({ basePath }: Props): React.ReactElement { items={itemsRef.current} /> - + + + - + diff --git a/packages/page-extrinsics/src/types.ts b/packages/page-extrinsics/src/types.ts new file mode 100644 index 0000000000..b7a27234d9 --- /dev/null +++ b/packages/page-extrinsics/src/types.ts @@ -0,0 +1,10 @@ +// Copyright 2017-2022 @polkadot/app-extrinsics authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { SubmittableExtrinsicFunction } from '@polkadot/api/types'; +import type { Call } from '@polkadot/types/interfaces'; + +export interface DecodedExtrinsic { + call: Call; + fn: SubmittableExtrinsicFunction<'promise'>; +} diff --git a/packages/react-components/src/Extrinsic.tsx b/packages/react-components/src/Extrinsic.tsx index 9b063650a4..b8eaa66064 100644 --- a/packages/react-components/src/Extrinsic.tsx +++ b/packages/react-components/src/Extrinsic.tsx @@ -16,6 +16,7 @@ import paramComponents from './Params'; interface Props { className?: string; + defaultArgs?: RawParam[]; defaultValue: SubmittableExtrinsicFunction<'promise'>; isDisabled?: boolean; isError?: boolean; @@ -28,15 +29,29 @@ interface Props { withLabel?: boolean; } -interface CallState { - fn: SubmittableExtrinsicFunction<'promise'>; - params: { - name: string; - type: TypeDef - }[]; +interface ParamDef { + name: string; + type: TypeDef; } -function getParams ({ meta }: SubmittableExtrinsicFunction<'promise'>): { name: string; type: TypeDef }[] { +interface CallState { + extrinsic: { + fn: SubmittableExtrinsicFunction<'promise'>; + params: ParamDef[]; + }, + values: RawParam[]; +} + +function isValuesValid (params: ParamDef[], values: RawParam[]): boolean { + return values.reduce((isValid, value): boolean => + isValid && + !isUndefined(value) && + !isUndefined(value.value) && + value.isValid, params.length === values.length + ); +} + +function getParams ({ meta }: SubmittableExtrinsicFunction<'promise'>): ParamDef[] { return meta.args.map(({ name, type, typeName }): { name: string; type: TypeDef } => ({ name: name.toString(), type: { @@ -49,21 +64,21 @@ function getParams ({ meta }: SubmittableExtrinsicFunction<'promise'>): { name: })); } -function ExtrinsicDisplay ({ defaultValue, isDisabled, isError, isPrivate, label, onChange, onEnter, onError, onEscape, withLabel }: Props): React.ReactElement { - const [extrinsic, setCall] = useState(() => ({ fn: defaultValue, params: getParams(defaultValue) })); - const [values, setValues] = useState([]); +function getCallState (fn: SubmittableExtrinsicFunction<'promise'>, values: RawParam[] = []): CallState { + return { + extrinsic: { + fn, + params: getParams(fn) + }, + values + }; +} + +function ExtrinsicDisplay ({ defaultArgs, defaultValue, isDisabled, isError, isPrivate, label, onChange, onEnter, onError, onEscape, withLabel }: Props): React.ReactElement { + const [{ extrinsic, values }, setDisplay] = useState(() => getCallState(defaultValue, defaultArgs)); useEffect((): void => { - setValues([]); - }, [extrinsic]); - - useEffect((): void => { - const isValid = values.reduce((isValid, value): boolean => - isValid && - !isUndefined(value) && - !isUndefined(value.value) && - value.isValid, extrinsic.params.length === values.length - ); + const isValid = isValuesValid(extrinsic.params, values); let method; @@ -81,7 +96,14 @@ function ExtrinsicDisplay ({ defaultValue, isDisabled, isError, isPrivate, label }, [extrinsic, onChange, onError, values]); const _onChangeMethod = useCallback( - (fn: SubmittableExtrinsicFunction<'promise'>): void => setCall({ fn, params: getParams(fn) }), + (fn: SubmittableExtrinsicFunction<'promise'>) => + setDisplay(getCallState(fn)), + [] + ); + + const _setValues = useCallback( + (values: RawParam[]) => + setDisplay(({ extrinsic }) => ({ extrinsic, values })), [] ); @@ -101,11 +123,12 @@ function ExtrinsicDisplay ({ defaultValue, isDisabled, isError, isPrivate, label /> ); diff --git a/packages/react-params/src/Param/Account.tsx b/packages/react-params/src/Param/Account.tsx index 510c77ba31..6b4dbc2aa8 100644 --- a/packages/react-params/src/Param/Account.tsx +++ b/packages/react-params/src/Param/Account.tsx @@ -10,28 +10,29 @@ import { keyring } from '@polkadot/ui-keyring'; import Bare from './Bare'; +function isValidAddress (value?: string | null): boolean { + if (value) { + try { + keyring.decodeAddress(value); + + return true; + } catch (err) { + console.error(err); + } + } + + return false; +} + function Account ({ className = '', defaultValue: { value }, isDisabled, isError, isInOption, label, onChange, withLabel }: Props): React.ReactElement { const [defaultValue] = useState(() => (value as string)?.toString()); const _onChange = useCallback( - (value?: string | null): void => { - let isValid = false; - - if (value) { - try { - keyring.decodeAddress(value); - - isValid = true; - } catch (err) { - console.error(err); - } - } - + (value?: string | null) => onChange && onChange({ - isValid, + isValid: isValidAddress(value), value - }); - }, + }), [onChange] ); diff --git a/packages/react-params/src/Param/Amount.tsx b/packages/react-params/src/Param/Amount.tsx index 3bedc983d4..d89fe55fd4 100644 --- a/packages/react-params/src/Param/Amount.tsx +++ b/packages/react-params/src/Param/Amount.tsx @@ -1,6 +1,7 @@ // Copyright 2017-2022 @polkadot/react-params authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { Registry, TypeDef } from '@polkadot/types/types'; import type { BN } from '@polkadot/util'; import type { Props } from '../types'; @@ -11,6 +12,14 @@ import { bnToBn, formatNumber, isUndefined } from '@polkadot/util'; import Bare from './Bare'; +function getBitLength (registry: Registry, { type }: TypeDef): number { + try { + return registry.createType(type as 'u32').bitLength(); + } catch (error) { + return 32; + } +} + function Amount ({ className = '', defaultValue: { value }, isDisabled, isError, label, onChange, onEnter, registry, type, withLabel }: Props): React.ReactElement { const defaultValue = useMemo( () => isDisabled @@ -22,21 +31,16 @@ function Amount ({ className = '', defaultValue: { value }, isDisabled, isError, ); const bitLength = useMemo( - (): number => { - try { - return registry.createType(type.type as 'u32').bitLength(); - } catch (error) { - return 32; - } - }, + () => getBitLength(registry, type), [registry, type] ); const _onChange = useCallback( - (value?: BN) => onChange && onChange({ - isValid: !isUndefined(value), - value - }), + (value?: BN) => + onChange && onChange({ + isValid: !isUndefined(value), + value + }), [onChange] ); diff --git a/packages/react-params/src/Param/BaseBytes.tsx b/packages/react-params/src/Param/BaseBytes.tsx index 95d2343cac..f34e40127a 100644 --- a/packages/react-params/src/Param/BaseBytes.tsx +++ b/packages/react-params/src/Param/BaseBytes.tsx @@ -78,7 +78,10 @@ function BaseBytes ({ asHex, children, className = '', defaultValue: { value }, : u8aToHex(value as Uint8Array, isDisabled ? 256 : -1) : undefined ); - const [{ isAddress, isValid, lastValue }, setValidity] = useState(() => ({ isAddress: false, isValid: false })); + const [{ isAddress, isValid, lastValue }, setValidity] = useState(() => ({ + isAddress: false, + isValid: isHex(defaultValue) + })); const _onChange = useCallback( (hex: string): void => { diff --git a/packages/react-params/src/Param/Enum.tsx b/packages/react-params/src/Param/Enum.tsx index 542e9a4303..0191c418ad 100644 --- a/packages/react-params/src/Param/Enum.tsx +++ b/packages/react-params/src/Param/Enum.tsx @@ -1,10 +1,10 @@ // Copyright 2017-2022 @polkadot/react-params authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { TypeDef } from '@polkadot/types/types'; +import type { Registry, TypeDef } from '@polkadot/types/types'; import type { ParamDef, Props, RawParam } from '../types'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { Dropdown } from '@polkadot/react-components'; import { Enum, getTypeDef } from '@polkadot/types'; @@ -23,40 +23,61 @@ interface Options { subTypes: TypeDef[]; } +interface Initial { + initialEnum: string | null; + initialValues: RawParam[] | undefined; +} + +function getSubTypes (registry: Registry, type: TypeDef): TypeDef[] { + const rawType = registry.createType(type.type as 'u32').toRawType(); + + return getTypeDef(rawType).sub as TypeDef[]; +} + +function getOptions (registry: Registry, type: TypeDef): Options { + const subTypes = getSubTypes(registry, type).filter(({ name }) => !!name && !name.startsWith('__Unused')); + + return { + options: subTypes.map(({ name }): Option => ({ + text: name, + value: name + })), + subTypes + }; +} + +function getInitial (defaultValue: RawParam): Initial { + return { + initialEnum: defaultValue && defaultValue.value + ? defaultValue.value instanceof Enum + ? defaultValue.value.type + : Object.keys(defaultValue.value as Record)[0] + : null, + initialValues: defaultValue && defaultValue.value + ? defaultValue.value instanceof Enum + ? [{ isValid: true, value: defaultValue.value.inner }] + : undefined + : undefined + }; +} + +function getCurrent (registry: Registry, type: TypeDef, defaultValue: RawParam, subTypes: TypeDef[]): ParamDef[] | null { + const subs = getSubTypes(registry, type); + + return defaultValue.value instanceof Enum + ? [{ name: defaultValue.value.type, type: subs[defaultValue.value.index] }] + : [{ name: subTypes[0].name, type: subTypes[0] }]; +} + function EnumParam (props: Props): React.ReactElement { const { className = '', defaultValue, isDisabled, isError, label, onChange, overrides, registry, type, withLabel } = props; - const [current, setCurrent] = useState(null); - const [initialValue, setInitialValue] = useState(null); - const [{ options, subTypes }, setOptions] = useState({ options: [], subTypes: [] }); - - useEffect((): void => { - const rawType = registry.createType(type.type as 'u32').toRawType(); - const typeDef = getTypeDef(rawType); - const subTypes = (typeDef.sub as TypeDef[]).filter(({ name }) => !!name && !name.startsWith('__Unused')); - - setOptions({ - options: subTypes.map(({ name }): Option => ({ - text: name, - value: name - })), - subTypes - }); - setCurrent([{ name: subTypes[0].name, type: subTypes[0] }]); - }, [registry, type]); - - useEffect((): void => { - setInitialValue( - defaultValue && defaultValue.value - ? defaultValue.value instanceof Enum - ? defaultValue.value.type - : Object.keys(defaultValue.value as Record)[0] - : null - ); - }, [defaultValue]); + const [{ options, subTypes }] = useState(() => getOptions(registry, type)); + const [current, setCurrent] = useState(() => getCurrent(registry, type, defaultValue, subTypes)); + const [{ initialEnum, initialValues }] = useState(() => getInitial(defaultValue)); const _onChange = useCallback( (value: string): void => { - const newType = subTypes.find(({ name }): boolean => name === value) || null; + const newType = subTypes.find(({ name }) => name === value) || null; setCurrent( newType @@ -85,7 +106,7 @@ function EnumParam (props: Props): React.ReactElement { { overrides={overrides} params={current} registry={registry} + values={initialValues} /> )} diff --git a/packages/react-params/src/Param/Struct.tsx b/packages/react-params/src/Param/Struct.tsx index b44b768f21..4528d1a533 100644 --- a/packages/react-params/src/Param/Struct.tsx +++ b/packages/react-params/src/Param/Struct.tsx @@ -3,16 +3,26 @@ import type { Props, RawParam } from '../types'; -import React, { useCallback } from 'react'; +import React, { useCallback, useState } from 'react'; + +import { Struct } from '@polkadot/types'; +import { isCodec } from '@polkadot/util'; import Params from '../'; import Base from './Base'; import Static from './Static'; import useParamDefs from './useParamDefs'; +function extractValues ({ isValid, value }: RawParam): RawParam[] | undefined { + return (isValid && isCodec(value) && value instanceof Struct) + ? value.toArray().map((value) => ({ isValid: true, value })) + : undefined; +} + function StructParam (props: Props): React.ReactElement { const params = useParamDefs(props.registry, props.type); - const { className = '', isDisabled, label, onChange, overrides, withLabel } = props; + const { className = '', defaultValue, isDisabled, label, onChange, overrides, withLabel } = props; + const [values] = useState(() => extractValues(defaultValue)); const _onChangeParams = useCallback( (values: RawParam[]): void => { @@ -44,6 +54,7 @@ function StructParam (props: Props): React.ReactElement { overrides={overrides} params={params} registry={props.registry} + values={values} /> ); diff --git a/packages/react-params/src/Param/Tuple.tsx b/packages/react-params/src/Param/Tuple.tsx index f7cf7b0693..787c315e8e 100644 --- a/packages/react-params/src/Param/Tuple.tsx +++ b/packages/react-params/src/Param/Tuple.tsx @@ -3,21 +3,30 @@ import type { Props, RawParam } from '../types'; -import React, { useCallback } from 'react'; +import React, { useCallback, useState } from 'react'; + +import { Tuple } from '@polkadot/types'; import Params from '../'; import Base from './Base'; import Static from './Static'; import useParamDefs from './useParamDefs'; -function Tuple (props: Props): React.ReactElement { +function getInitialValues ({ value }: RawParam): RawParam[] { + return value instanceof Tuple + ? value.map((value) => ({ isValid: true, value: value as unknown })) + : value as RawParam[]; +} + +function TupleDisplay (props: Props): React.ReactElement { const params = useParamDefs(props.registry, props.type); - const { className = '', isDisabled, label, onChange, overrides, withLabel } = props; + const { className = '', defaultValue, isDisabled, label, onChange, overrides, withLabel } = props; + const [values] = useState(() => getInitialValues(defaultValue)); const _onChangeParams = useCallback( (values: RawParam[]): void => { onChange && onChange({ - isValid: values.reduce((result: boolean, { isValid }) => result && isValid, true), + isValid: values.reduce((result, { isValid }) => result && isValid, true), value: values.map(({ value }) => value) }); }, @@ -40,9 +49,10 @@ function Tuple (props: Props): React.ReactElement { overrides={overrides} params={params} registry={props.registry} + values={values} /> ); } -export default React.memo(Tuple); +export default React.memo(TupleDisplay); diff --git a/packages/react-params/src/Param/Vector.tsx b/packages/react-params/src/Param/Vector.tsx index 485f589dc0..8b0f4a119d 100644 --- a/packages/react-params/src/Param/Vector.tsx +++ b/packages/react-params/src/Param/Vector.tsx @@ -14,32 +14,48 @@ import Params from '../'; import Base from './Base'; import useParamDefs from './useParamDefs'; -function generateParam ([{ name, type }]: ParamDef[], index: number): ParamDef { +function getParam ([{ name, type }]: ParamDef[], index: number): ParamDef { return { name: `${index}: ${name || type.type}`, type }; } +export function getParams (inputParams: ParamDef[], prev: ParamDef[], max: number): ParamDef[] { + if (prev.length === max) { + return prev; + } + + const params: ParamDef[] = []; + + for (let index = 0; index < max; index++) { + params.push(getParam(inputParams, index)); + } + + return params; +} + +export function getValues ({ value }: RawParam): RawParam[] { + return (value as RawParam[] || []).map((value: RawParam) => + isUndefined(value) || isUndefined(value.isValid) + ? { isValid: !isUndefined(value), value } + : value + ); +} + function Vector ({ className = '', defaultValue, isDisabled = false, label, onChange, overrides, registry, type, withLabel }: Props): React.ReactElement | null { const { t } = useTranslation(); const inputParams = useParamDefs(registry, type); - const [count, setCount] = useState(0); - const [params, setParams] = useState([]); - const [values, setValues] = useState([]); + const [values, setValues] = useState(() => getValues(defaultValue)); + const [count, setCount] = useState(() => values.length); + const [params, setParams] = useState(() => getParams(inputParams, [], count)); // build up the list of parameters we are using useEffect((): void => { - if (inputParams.length) { - const max = isDisabled ? (defaultValue.value as RawParam[] || []).length : count; - const params: ParamDef[] = []; - - for (let index = 0; index < max; index++) { - params.push(generateParam(inputParams, index)); - } - - setParams(params); - } + inputParams.length && + setParams((prev) => + getParams(inputParams, prev, isDisabled ? (defaultValue.value as RawParam[] || []).length : count) + ); }, [count, defaultValue, isDisabled, inputParams]); // when !isDisable, generating an input list based on count @@ -58,24 +74,12 @@ function Vector ({ className = '', defaultValue, isDisabled = false, label, onCh return values.slice(0, count); }); - }, [count, inputParams, isDisabled, registry]); - - // when isDisabled, set the values based on the defaultValue input - useEffect((): void => { - isDisabled && - setValues( - (defaultValue.value as RawParam[] || []).map((value: RawParam) => - isUndefined(value) || isUndefined(value.isValid) - ? { isValid: !isUndefined(value), value } - : value - ) - ); - }, [defaultValue, isDisabled]); + }, [count, defaultValue, inputParams, isDisabled, registry]); // when our values has changed, alert upstream useEffect((): void => { onChange && onChange({ - isValid: values.reduce((result: boolean, { isValid }) => result && isValid, true), + isValid: values.reduce((result, { isValid }) => result && isValid, true), value: values.map(({ value }) => value) }); }, [values, onChange]); diff --git a/packages/react-params/src/Param/VectorFixed.tsx b/packages/react-params/src/Param/VectorFixed.tsx index 2a6a14e36a..02f2b05a61 100644 --- a/packages/react-params/src/Param/VectorFixed.tsx +++ b/packages/react-params/src/Param/VectorFixed.tsx @@ -5,39 +5,25 @@ import type { ParamDef, Props, RawParam } from '../types'; import React, { useEffect, useState } from 'react'; +import { VecFixed } from '@polkadot/types'; import { isUndefined } from '@polkadot/util'; import getInitValue from '../initValue'; import Params from '../'; import Base from './Base'; import useParamDefs from './useParamDefs'; +import { getParams, getValues } from './Vector'; -function generateParam ([{ name, type }]: ParamDef[], index: number): ParamDef { - return { - name: `${index}: ${name || type.type}`, - type - }; +function getInitialValues (defaultValue: RawParam): RawParam[] { + return defaultValue.value instanceof VecFixed + ? defaultValue.value.map((value) => ({ isValid: true, value: value as unknown })) + : getValues(defaultValue); } function VectorFixed ({ className = '', defaultValue, isDisabled = false, label, onChange, overrides, registry, type, withLabel }: Props): React.ReactElement | null { const inputParams = useParamDefs(registry, type); - const [params, setParams] = useState([]); - const [values, setValues] = useState([]); - - // build up the list of parameters we are using - useEffect((): void => { - if (inputParams.length) { - const count = (inputParams[0].length || 1); - const max = isDisabled ? (defaultValue.value as RawParam[] || []).length : count; - const params: ParamDef[] = []; - - for (let index = 0; index < max; index++) { - params.push(generateParam(inputParams, index)); - } - - setParams(params); - } - }, [defaultValue, isDisabled, inputParams]); + const [params] = useState(() => getParams(inputParams, [], (inputParams[0].length || 1))); + const [values, setValues] = useState(() => getInitialValues(defaultValue)); // when !isDisable, generating an input list based on count useEffect((): void => { @@ -59,18 +45,6 @@ function VectorFixed ({ className = '', defaultValue, isDisabled = false, label, }); }, [inputParams, isDisabled, registry]); - // when isDisabled, set the values based on the defaultValue input - useEffect((): void => { - isDisabled && - setValues( - (defaultValue.value as RawParam[] || []).map((value: RawParam) => - isUndefined(value) || isUndefined(value.isValid) - ? { isValid: !isUndefined(value), value } - : value - ) - ); - }, [defaultValue, isDisabled]); - // when our values has changed, alert upstream useEffect((): void => { onChange && onChange({ diff --git a/packages/react-params/src/Param/useParamDefs.ts b/packages/react-params/src/Param/useParamDefs.ts index e37d4e6afb..e7c88968bb 100644 --- a/packages/react-params/src/Param/useParamDefs.ts +++ b/packages/react-params/src/Param/useParamDefs.ts @@ -4,7 +4,7 @@ import type { Registry, TypeDef } from '@polkadot/types/types'; import type { ParamDef } from '../types'; -import { useEffect, useState } from 'react'; +import { useMemo } from 'react'; import { createNamedHook } from '@polkadot/react-hooks'; import { getTypeDef } from '@polkadot/types/create'; @@ -19,26 +19,23 @@ function expandDef (registry: Registry, td: TypeDef): TypeDef { } } +function getDefs (registry: Registry, type: TypeDef): ParamDef[] { + const typeDef = expandDef(registry, type); + + return typeDef.sub + ? (Array.isArray(typeDef.sub) ? typeDef.sub : [typeDef.sub]).map((td): ParamDef => ({ + length: typeDef.length, + name: td.name, + type: td + })) + : []; +} + function useParamDefsImpl (registry: Registry, type: TypeDef): ParamDef[] { - const [params, setParams] = useState([]); - - useEffect((): void => { - const typeDef = expandDef(registry, type); - - if (!typeDef.sub) { - return setParams([]); - } - - setParams( - (Array.isArray(typeDef.sub) ? typeDef.sub : [typeDef.sub]).map((td): ParamDef => ({ - length: typeDef.length, - name: td.name, - type: td // expandDef(td) - })) - ); - }, [registry, type]); - - return params; + return useMemo( + () => getDefs(registry, type), + [registry, type] + ); } export default createNamedHook('useParamDefs', useParamDefsImpl); diff --git a/packages/react-params/src/index.tsx b/packages/react-params/src/index.tsx index 6d3af5b362..ae4e4bf5e5 100644 --- a/packages/react-params/src/index.tsx +++ b/packages/react-params/src/index.tsx @@ -50,13 +50,15 @@ class Params extends React.PureComponent { return { params, values: params.reduce( - (result: RawParams, param, index): RawParams => [ - ...result, - values && values[index] - ? values[index] - : createValue(registry, param) - ], - [] + (result: RawParams, param, index): RawParams => { + result.push( + values && values[index] + ? values[index] + : createValue(registry, param) + ); + + return result; + }, [] ) }; }