diff --git a/packages/page-contracts/src/Contracts/Call.tsx b/packages/page-contracts/src/Contracts/Call.tsx index 1b51dd9c41..83677e8619 100644 --- a/packages/page-contracts/src/Contracts/Call.tsx +++ b/packages/page-contracts/src/Contracts/Call.tsx @@ -10,8 +10,7 @@ import React, { useCallback, useState, useEffect, useMemo } from 'react'; import styled from 'styled-components'; import { Button, Dropdown, IconLink, InputAddress, InputBalance, Modal, TxButton } from '@polkadot/react-components'; import { PromiseContract as ApiContract } from '@polkadot/api-contract'; -import { useAccountId, useFormField, useToggle } from '@polkadot/react-hooks'; -import { createValue } from '@polkadot/react-params/values'; +import { useAccountId, useFormField } from '@polkadot/react-hooks'; import { BN_ZERO } from '@polkadot/util'; import { InputMegaGas, Params } from '../shared'; @@ -34,23 +33,24 @@ function Call ({ callContract, callMessageIndex, className = '', onChangeCallCon const callMessage = callContract.abi.messages[callMessageIndex]; const [accountId, setAccountId] = useAccountId(); const [endowment, isEndowmentValid, setEndowment] = useFormField(BN_ZERO); - const [isBusy, , setIsBusy] = useToggle(); const [outcomes, setOutcomes] = useState([]); const [execTx, setExecTx] = useState | null>(null); - const [params, setParams] = useState(callMessage ? callMessage.args.map(({ type }) => createValue({ type })) : []); + const [params, setParams] = useState([]); const useWeightHook = useWeight(); const { isValid: isWeightValid, weight } = useWeightHook; useEffect((): void => { - const callMessage = callContract.abi.messages[callMessageIndex]; - - setParams(callMessage ? callMessage.args.map(({ type }) => createValue({ type })) : []); + setParams([]); }, [callContract, callMessageIndex]); useEffect((): void => { - endowment && callContract.hasRpcContractsCall && callMessage.isMutating && setExecTx( - () => callContract.exec(callMessage, endowment, weight, ...params) - ); + endowment && callMessage.isMutating && setExecTx((): SubmittableExtrinsic<'promise'> | null => { + try { + return callContract.exec(callMessage, callMessage.isPayable ? endowment : 0, weight, ...params); + } catch (error) { + return null; + } + }); }, [accountId, callContract, callMessage, endowment, weight, params]); const _onChangeCallMessageIndexString = useCallback( @@ -104,7 +104,6 @@ function Call ({ callContract, callMessageIndex, className = '', onChangeCallCon ('Specify the user account to use for this contract call. And fees will be deducted from this account.')} - isDisabled={isBusy} label={t('call from account')} onChange={setAccountId} type='account' @@ -112,7 +111,6 @@ function Call ({ callContract, callMessageIndex, className = '', onChangeCallCon /> ('A deployed contract that has either been deployed or attached. The address and ABI are used to construct the parameters.')} - isDisabled={isBusy} label={t('contract to use')} onChange={onChangeCallContractAddress} type='contract' @@ -123,7 +121,6 @@ function Call ({ callContract, callMessageIndex, className = '', onChangeCallCon ('The message to send to this contract. Parameters are adjusted based on the ABI provided.')} - isDisabled={isBusy} isError={callMessage === null} label={t('message to send')} onChange={_onChangeCallMessageIndexString} @@ -131,7 +128,6 @@ function Call ({ callContract, callMessageIndex, className = '', onChangeCallCon value={`${callMessageIndex}`} /> )} - {!isViaRpc && ( + {!isViaRpc && callMessage.isPayable && ( ('The allotted value for this contract, i.e. the amount transferred to the contract as part of this call.')} - isDisabled={isBusy} isError={!isEndowmentValid} isZeroable label={t('value')} @@ -197,9 +192,6 @@ function Call ({ callContract, callMessageIndex, className = '', onChangeCallCon icon='sign-in-alt' isDisabled={!isValid} label={t('Execute')} - onClick={(): void => setIsBusy(true)} - onFailed={(): void => setIsBusy(false)} - onSuccess={(): void => setIsBusy(false)} withSpinner /> ) diff --git a/packages/react-components/src/TxButton.tsx b/packages/react-components/src/TxButton.tsx index 7202054ae9..2debed63da 100644 --- a/packages/react-components/src/TxButton.tsx +++ b/packages/react-components/src/TxButton.tsx @@ -6,7 +6,7 @@ import { TxButtonProps as Props } from './types'; import React, { useCallback, useContext, useEffect, useState } from 'react'; import { SubmittableResult } from '@polkadot/api'; -import { useApi } from '@polkadot/react-hooks'; +import { useApi, useIsMountedRef } from '@polkadot/react-hooks'; import { assert, isFunction } from '@polkadot/util'; import Button from './Button'; @@ -16,6 +16,7 @@ import { useTranslation } from './translate'; function TxButton ({ accountId, className = '', extrinsic: propsExtrinsic, icon, isBasic, isBusy, isDisabled, isIcon, isToplevel, isUnsigned, label, onClick, onFailed, onSendRef, onStart, onSuccess, onUpdate, params, tooltip, tx, withSpinner, withoutLink }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); + const mountedRef = useIsMountedRef(); const { queueExtrinsic } = useContext(StatusContext); const [isSending, setIsSending] = useState(false); const [isStarted, setIsStarted] = useState(false); @@ -27,25 +28,27 @@ function TxButton ({ accountId, className = '', extrinsic: propsExtrinsic, icon, const _onFailed = useCallback( (result: SubmittableResult | null): void => { - setIsSending(false); + mountedRef.current && setIsSending(false); onFailed && onFailed(result); }, - [onFailed, setIsSending] + [onFailed, setIsSending, mountedRef] ); const _onSuccess = useCallback( (result: SubmittableResult): void => { - setIsSending(false); + mountedRef.current && setIsSending(false); onSuccess && onSuccess(result); }, - [onSuccess, setIsSending] + [onSuccess, setIsSending, mountedRef] ); const _onStart = useCallback( - () => setIsStarted(true), - [setIsStarted] + (): void => { + mountedRef.current && setIsStarted(true); + }, + [setIsStarted, mountedRef] ); const _onSend = useCallback( @@ -72,9 +75,7 @@ function TxButton ({ accountId, className = '', extrinsic: propsExtrinsic, icon, assert(extrinsics?.length, 'Expected generated extrinsic passed to TxButton'); - if (withSpinner) { - setIsSending(true); - } + mountedRef.current && withSpinner && setIsSending(true); extrinsics.forEach((extrinsic): void => { queueExtrinsic({ @@ -90,7 +91,7 @@ function TxButton ({ accountId, className = '', extrinsic: propsExtrinsic, icon, onClick && onClick(); }, - [_onFailed, _onStart, _onSuccess, accountId, api.tx, isUnsigned, onClick, onFailed, onSuccess, onUpdate, params, propsExtrinsic, queueExtrinsic, setIsSending, tx, withSpinner] + [_onFailed, _onStart, _onSuccess, accountId, api.tx, isUnsigned, onClick, onFailed, onSuccess, onUpdate, params, propsExtrinsic, queueExtrinsic, setIsSending, tx, withSpinner, mountedRef] ); if (onSendRef) { diff --git a/packages/react-hooks/src/useToggle.ts b/packages/react-hooks/src/useToggle.ts index 42da4e6da5..e216a641b8 100644 --- a/packages/react-hooks/src/useToggle.ts +++ b/packages/react-hooks/src/useToggle.ts @@ -3,13 +3,25 @@ import { useCallback, useEffect, useState } from 'react'; +import useIsMountedRef from './useIsMountedRef'; + // Simple wrapper for a true/false toggle export default function useToggle (defaultValue = false, onToggle?: (isActive: boolean) => void): [boolean, () => void, (value: boolean) => void] { + const mountedRef = useIsMountedRef(); const [isActive, setActive] = useState(defaultValue); - const toggleActive = useCallback( - () => setActive((isActive: boolean) => !isActive), - [] + const _toggleActive = useCallback( + (): void => { + mountedRef.current && setActive((isActive) => !isActive); + }, + [mountedRef] + ); + + const _setActive = useCallback( + (isActive: boolean): void => { + mountedRef.current && setActive(isActive); + }, + [mountedRef] ); useEffect( @@ -17,5 +29,5 @@ export default function useToggle (defaultValue = false, onToggle?: (isActive: b [isActive, onToggle] ); - return [isActive, toggleActive, setActive]; + return [isActive, _toggleActive, _setActive]; }