Exhaustive hooks (#2463)

* Exhaustive hooks

* selectUrl screw-up

* Adjust name badge

* Cleanup SideBar effects
This commit is contained in:
Jaco Greeff 2020-03-27 12:49:17 +01:00 committed by GitHub
parent 1f79d75df7
commit e4c0cfd262
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
98 changed files with 734 additions and 776 deletions

View File

@ -8,10 +8,10 @@
"packages/*"
],
"resolutions": {
"@polkadot/api": "^1.9.0-beta.11",
"@polkadot/api-contract": "^1.9.0-beta.11",
"@polkadot/api": "^1.9.0-beta.12",
"@polkadot/api-contract": "^1.9.0-beta.12",
"@polkadot/keyring": "^2.7.0-beta.8",
"@polkadot/types": "^1.9.0-beta.11",
"@polkadot/types": "^1.9.0-beta.12",
"@polkadot/util": "^2.7.0-beta.8",
"@polkadot/util-crypto": "^2.7.0-beta.8",
"babel-core": "^7.0.0-bridge.0",
@ -36,7 +36,7 @@
"@babel/core": "^7.9.0",
"@babel/register": "^7.9.0",
"@babel/runtime": "^7.9.2",
"@polkadot/dev": "^0.51.8",
"@polkadot/dev": "^0.51.9",
"@polkadot/ts": "^0.3.14",
"@types/bn.js": "^4.11.6",
"@types/chart.js": "^2.9.16",

View File

@ -15,7 +15,7 @@
"dependencies": {
"@babel/polyfill": "^7.8.7",
"@babel/runtime": "^7.9.2",
"@polkadot/dev": "^0.51.8",
"@polkadot/dev": "^0.51.9",
"@polkadot/react-components": "0.40.0-beta.191",
"@polkadot/react-signer": "0.40.0-beta.191",
"query-string": "^6.11.1"

View File

@ -29,6 +29,10 @@ interface SidebarState {
export const PORTAL_ID = 'portals';
function saveSidebar (sidebar: SidebarState): SidebarState {
return store.set('sidebar', sidebar);
}
function Apps ({ className }: Props): React.ReactElement<Props> {
const { systemChain, systemName } = useApi();
const [sidebar, setSidebar] = useState<SidebarState>({
@ -38,24 +42,17 @@ function Apps ({ className }: Props): React.ReactElement<Props> {
...store.get('sidebar', {}),
isMenu: window.innerWidth < SIDEBAR_MENU_THRESHOLD
});
const uiHighlight = useMemo((): string | undefined =>
getSystemChainColor(systemChain, systemName), [systemChain, systemName]
const uiHighlight = useMemo(
(): string | undefined => getSystemChainColor(systemChain, systemName),
[systemChain, systemName]
);
const { isCollapsed, isMenu, isMenuOpen } = sidebar;
const _setSidebar = useCallback(
(update: Partial<SidebarState>): void =>
setSidebar((sidebar: SidebarState) =>
store.set('sidebar', { ...sidebar, ...update })
),
const _collapse = useCallback(
(): void => setSidebar((sidebar: SidebarState) => saveSidebar({ ...sidebar, isCollapsed: !sidebar.isCollapsed })),
[]
);
const _collapse = useCallback(
(): void => _setSidebar({ isCollapsed: !isCollapsed }),
[isCollapsed]
);
const _toggleMenu = useCallback(
(): void => _setSidebar({ isCollapsed: false, isMenuOpen: true }),
(): void => setSidebar((sidebar: SidebarState) => saveSidebar({ ...sidebar, isCollapsed: false, isMenuOpen: true })),
[]
);
const _handleResize = useCallback(
@ -64,15 +61,18 @@ function Apps ({ className }: Props): React.ReactElement<Props> {
? SideBarTransition.MINIMISED_AND_EXPANDED
: SideBarTransition.EXPANDED_AND_MAXIMISED;
_setSidebar({
setSidebar((sidebar: SidebarState) => saveSidebar({
...sidebar,
isMenu: transition === SideBarTransition.MINIMISED_AND_EXPANDED,
isMenuOpen: false,
transition
});
}));
},
[]
);
const { isCollapsed, isMenu, isMenuOpen } = sidebar;
return (
<>
<GlobalStyle uiHighlight={defaultColor || uiHighlight} />

View File

@ -74,7 +74,7 @@ function Status ({ optionsAll, queueAction, stqueue, txqueue }: Props): React.Re
const filtered = filterEvents(allAccounts, t, optionsAll, events);
filtered && queueAction(filtered);
}, [allAccounts, events, optionsAll]);
}, [allAccounts, events, optionsAll, queueAction, t]);
return (
<StatusDisplay

View File

@ -74,30 +74,31 @@ function checkVisible (name: string, { api, isApiReady, isApiConnected }: ApiPro
}
function Item ({ route, isCollapsed, onClick }: Props): React.ReactElement<Props> | null {
const { Modal, useCounter = DUMMY_COUNTER, display, i18n, icon, name } = route;
const { t } = useTranslation();
const { allAccounts, hasAccounts } = useAccounts();
const apiProps = useApi();
const sudoKey = useCall<AccountId>(apiProps.isApiReady && apiProps.api.query.sudo?.key, []);
const [hasSudo, setHasSudo] = useState(false);
const [isVisible, setIsVisible] = useState(false);
const count = useCounter();
const count = (route.useCounter || DUMMY_COUNTER)();
useEffect((): void => {
setHasSudo(!!sudoKey && allAccounts.some((address): boolean => sudoKey.eq(address)));
}, [allAccounts, sudoKey]);
useEffect((): void => {
const isVisible = checkVisible(name, apiProps, hasAccounts, hasSudo, display);
const isVisible = checkVisible(route.name, apiProps, hasAccounts, hasSudo, route.display);
route.isIgnored = !isVisible;
setIsVisible(isVisible);
}, [apiProps, hasAccounts, hasSudo]);
}, [apiProps, hasAccounts, hasSudo, route]);
if (!isVisible) {
return null;
}
const { Modal, i18n, icon, name } = route;
const body = (
<>
<Icon name={icon} />

View File

@ -16,6 +16,7 @@ function WarmUp (): React.ReactElement {
useEffect((): void => {
setHasValues(!!fees || !!indexes || !!issuance || !!registrars || !!staking);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (

View File

@ -15,7 +15,10 @@ interface Props {
function AccountSelector ({ className, onChange }: Props): React.ReactElement<Props> {
const [accountId, setAccountId] = useState<string | null>(null);
useEffect((): void => onChange(accountId), [accountId]);
useEffect(
(): void => onChange(accountId),
[accountId, onChange]
);
return (
<section className={`template--AccountSelector ui--row ${className}`}>

View File

@ -71,7 +71,7 @@ function Account ({ address, className, filter, isFavorite, toggleFavorite }: Pr
} else if (nickname) {
setAccName(nickname);
}
}, [info]);
}, [api, info]);
useEffect((): void => {
const account = keyring.getAccount(address);
@ -84,7 +84,7 @@ function Account ({ address, className, filter, isFavorite, toggleFavorite }: Pr
});
_setTags(account?.meta.tags || []);
setAccName(account?.meta.name || '');
}, [address]);
}, [address, _setTags]);
useEffect((): void => {
if (filter.length === 0) {

View File

@ -92,14 +92,14 @@ function Derive ({ className, from, onClose }: Props): React.ReactElement {
const deriveError = deriveValidate(debouncedSuri, source.type);
if (!deriveError) {
const result = source.derive(suri);
const result = source.derive(debouncedSuri);
address = result.address;
}
return { address, deriveError };
});
}, [debouncedSuri]);
}, [debouncedSuri, source]);
const _onChangeName = (name: string): void =>
setName({ isNameValid: !!name.trim(), name });

View File

@ -36,7 +36,7 @@ function Transfer ({ className, onClose, recipientId: propRecipientId, senderId:
senderId && recipientId && setExtrinsic(
() => api.tx.balances.transfer(recipientId, amount || ZERO)
);
}, [amount, recipientId, senderId]);
}, [api, amount, recipientId, senderId]);
const transferrable = <span className='label'>{t('transferrable')}</span>;

View File

@ -45,6 +45,14 @@ function Address ({ address, className, filter, isFavorite, toggleFavorite }: Pr
const _setTags = (tags: string[]): void => setTags(tags.sort());
useEffect((): void => {
const current = keyring.getAddress(address);
setCurrent(current || null);
setGenesisHash((current && current.meta.genesisHash) || null);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect((): void => {
const { identity, nickname } = info || {};
@ -55,14 +63,7 @@ function Address ({ address, className, filter, isFavorite, toggleFavorite }: Pr
} else if (nickname) {
setAccName(nickname);
}
}, [info]);
useEffect((): void => {
const current = keyring.getAddress(address);
setCurrent(current || null);
setGenesisHash((current && current.meta.genesisHash) || null);
}, []);
}, [api, info]);
useEffect((): void => {
const account = keyring.getAddress(address);

View File

@ -25,11 +25,11 @@ function Match ({ address, className, count, offset, onCreateToggle, onRemove, s
);
const _onCreate = useCallback(
(): void => onCreateToggle(hexSeed),
[hexSeed]
[hexSeed, onCreateToggle]
);
const _onRemove = useCallback(
(): void => onRemove(address),
[address]
[address, onRemove]
);
return (

View File

@ -5,7 +5,7 @@
import { Option } from '@polkadot/types';
import { BalanceOf, EthereumAddress } from '@polkadot/types/interfaces';
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
import { Button, Card } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
@ -27,24 +27,26 @@ function Claim ({ button, className, ethereumAddress }: Props): React.ReactEleme
const [claimAddress, setClaimAddress] = useState<EthereumAddress | null>(null);
const [isBusy, setIsBusy] = useState(false);
const _fetchClaim = (address: EthereumAddress): void => {
setIsBusy(true);
const _fetchClaim = useCallback(
(address: EthereumAddress): void => {
setIsBusy(true);
api.query.claims
.claims<Option<BalanceOf>>(address)
.then((claim): void => {
setClaimValue(claim.unwrapOr(null));
setIsBusy(false);
})
.catch((): void => setIsBusy(false));
};
api.query.claims
.claims<Option<BalanceOf>>(address)
.then((claim): void => {
setClaimValue(claim.unwrapOr(null));
setIsBusy(false);
})
.catch((): void => setIsBusy(false));
},
[api]
);
useEffect((): void => {
if (ethereumAddress !== claimAddress) {
setClaimAddress(ethereumAddress);
ethereumAddress && _fetchClaim(ethereumAddress);
}
}, [ethereumAddress]);
setClaimAddress(ethereumAddress);
ethereumAddress && _fetchClaim(ethereumAddress);
}, [_fetchClaim, ethereumAddress]);
if (isBusy || !claimAddress) {
return null;

View File

@ -12,6 +12,6 @@
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.9.2",
"@polkadot/api-contract": "^1.9.0-beta.11"
"@polkadot/api-contract": "^1.9.0-beta.12"
}
}

View File

@ -33,13 +33,8 @@ interface Props extends BareProps {
function Call (props: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { className, isOpen, callContract, callMessageIndex, onChangeCallContractAddress, onChangeCallMessageIndex, onClose } = props;
if (isNull(callContract) || isNull(callMessageIndex)) {
return null;
}
const hasRpc = callContract.hasRpcContractsCall;
let callMessage = callContract.getMessage(callMessageIndex);
const hasRpc = callContract?.hasRpcContractsCall;
const callMessage = callContract?.getMessage(isNull(callMessageIndex) ? undefined : callMessageIndex);
const { api } = useApi();
const [accountId, setAccountId] = useState<StringOrNull>(null);
@ -51,23 +46,29 @@ function Call (props: Props): React.ReactElement<Props> | null {
const [useRpc, setUseRpc] = useState(hasRpc && callMessage && !callMessage.def.mutates);
useEffect((): void => {
callMessage = callContract.getMessage(callMessageIndex);
if (callContract && callMessageIndex) {
const callMessage = callContract.getMessage(callMessageIndex);
setParams(callMessage ? callMessage.def.args.map(({ type }): any => createValue({ type })) : []);
setParams(callMessage ? callMessage.def.args.map(({ type }): any => createValue({ type })) : []);
if (hasRpc) {
if (!callMessage || callMessage.def.mutates) {
setUseRpc(false);
} else {
setUseRpc(true);
if (hasRpc) {
if (!callMessage || callMessage.def.mutates) {
setUseRpc(false);
} else {
setUseRpc(true);
}
}
}
}, [callContract, callMessageIndex]);
}, [callContract, callMessageIndex, hasRpc]);
useEffect((): void => {
setOutcomes([]);
}, [callContract]);
if (isNull(callContract) || isNull(callMessageIndex) || !callMessage) {
return null;
}
const _onChangeAccountId = (accountId: StringOrNull): void => setAccountId(accountId);
const _onChangeCallMessageIndexString = (callMessageIndexString: string): void => {
@ -91,7 +92,7 @@ function Call (props: Props): React.ReactElement<Props> | null {
};
const _onSubmitRpc = (): void => {
if (!accountId) return;
if (!accountId || !callMessage) return;
callContract
.call('rpc', callMessage.def.name, endowment, gasLimit, ...params)

View File

@ -31,13 +31,12 @@ const ContractCard = styled(Card)`
function Contract (props: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { contract: { abi, address }, onCall } = props;
const [isForgetOpen, setIsForgetOpen] = useState(false);
if (!address || !abi) {
return null;
}
const [isForgetOpen, setIsForgetOpen] = useState(false);
const _toggleForget = (): void => setIsForgetOpen(!isForgetOpen);
const _onForget = (): void => {
if (!address) {

View File

@ -39,7 +39,7 @@ function ValidateAddr ({ address, onChange }: Props): React.ReactElement<Props>
useEffect((): void => {
onChange(isAddress && isStored);
}, [isAddress, isStored]);
}, [isAddress, isStored, onChange]);
if (isStored || !isAddress) {
return null;

View File

@ -35,13 +35,9 @@ function Contracts (props: Props): React.ReactElement<Props> {
useEffect((): void => {
setContracts(filterContracts(api, props));
}, [accounts, api, keyringContracts]);
}, [accounts, api, keyringContracts, props]);
let callContract = contracts[callContractIndex] || null;
useEffect((): void => {
callContract = contracts[callContractIndex];
}, [callContractIndex]);
const callContract = contracts[callContractIndex] || null;
const _toggleAdd = (): void => setIsAddOpen(!isAddOpen);
const _toggleCall = (): void => setIsCallOpen(!isCallOpen);

View File

@ -51,8 +51,7 @@ function onSelectConstructor (props: Props, index: number): () => void {
};
}
function renderItem (props: Props, message: ContractABIMessage, index: number, asConstructor = false): React.ReactNode {
const { t } = useTranslation();
function renderItem (props: Props, message: ContractABIMessage, index: number, asConstructor: boolean, t: (key: string) => string): React.ReactNode {
const { docs = [], name } = message;
return (
@ -108,24 +107,24 @@ function renderItem (props: Props, message: ContractABIMessage, index: number, a
);
}
function renderConstructor (props: Props, index: number): React.ReactNode {
function renderConstructor (props: Props, index: number, t: (key: string) => string): React.ReactNode {
const { contractAbi: { abi: { contract: { constructors } } } } = props;
if (!constructors[index]) {
return null;
}
return renderItem(props, constructors[index], index, true);
return renderItem(props, constructors[index], index, true, t);
}
function renderMessage (props: Props, index: number): React.ReactNode {
function renderMessage (props: Props, index: number, t: (key: string) => string): React.ReactNode {
const { contractAbi: { abi: { contract: { messages } } } } = props;
if (!messages[index]) {
return null;
}
return renderItem(props, messages[index], index);
return renderItem(props, messages[index], index, false, t);
}
function Messages (props: Props): React.ReactElement<Props> {
@ -134,8 +133,8 @@ function Messages (props: Props): React.ReactElement<Props> {
return (
<div className={classes(className, 'ui--Messages', isLabelled && 'labelled')}>
{withConstructors && constructors.map((_, index): React.ReactNode => renderConstructor(props, index))}
{messages.map((_, index): React.ReactNode => renderMessage(props, index))}
{withConstructors && constructors.map((_, index): React.ReactNode => renderConstructor(props, index, t))}
{messages.map((_, index): React.ReactNode => renderMessage(props, index, t))}
{isRemovable && (
<IconLink
label={t('Remove ABI')}

View File

@ -37,7 +37,7 @@ function ProposeExternal ({ className, isMember, members }: Props): React.ReactE
} else {
setProposal(null);
}
}, [hash, isHashValid]);
}, [api, hash, isHashValid]);
return (
<>

View File

@ -49,7 +49,7 @@ function Slashing ({ className, isMember, members }: Props): React.ReactElement<
} else {
setEras([]);
}
}, [slashes]);
}, [slashes, t]);
useEffect((): void => {
const actioned = selectedEra && slashes?.find(([era]): boolean => era.eqn(selectedEra));
@ -59,7 +59,7 @@ function Slashing ({ className, isMember, members }: Props): React.ReactElement<
? api.tx.staking.cancelDeferredSlash(actioned[0], actioned[1].map((_, index): number => index))
: null
);
}, [selectedEra, slashes]);
}, [api, selectedEra, slashes]);
return (
<>

View File

@ -48,7 +48,7 @@ export default function useAvailableSlashes (): [BN, UnappliedSlash[]][] {
return (): void => {
unsub && unsub();
};
}, [earliestSlash, indexes]);
}, [api, earliestSlash, indexes, mountedRef]);
return slashes;
}

View File

@ -60,9 +60,11 @@ function Overview ({ className, prime }: Props): React.ReactElement<Props> {
const bestNumber = useCall<BlockNumber>(api.derive.chain.bestNumber, []);
const electionsInfo = useCall<DerivedElectionsInfo>(api.derive.elections.info, []);
const allVotes = api.query.electionsPhragmen?.votesOf.creator.meta.type.asMap.linked.isTrue
// eslint-disable-next-line react-hooks/rules-of-hooks
? useCall<Record<string, AccountId[]>>(api.query.electionsPhragmen?.votesOf, [], {
transform: transformVotesPrev
})
// eslint-disable-next-line react-hooks/rules-of-hooks
: useCall<Record<string, AccountId[]>>(api.query.electionsPhragmen?.votesOf.entries as any, [], {
transform: transformVotes
});

View File

@ -33,7 +33,7 @@ function DispatchEntry ({ blockNumber, hash, referendumIndex }: Props): React.Re
useEffect((): void => {
preimage?.isSome && setProposal(api.createType('Proposal', preimage.unwrap()[0].toU8a(true)));
}, [preimage]);
}, [api, preimage]);
return (
<tr>

View File

@ -49,7 +49,7 @@ function Externals ({ className }: Props): React.ReactElement<Props> | null {
} else {
setExpanded(null);
}
}, [preimage]);
}, [api, preimage]);
if (!hash) {
return null;

View File

@ -46,7 +46,7 @@ function Fasttrack ({ hash, threshold }: Props): React.ReactElement<Props> | nul
? api.tx.democracy.fastTrack(hash, votingBlocks, delayBlocks)
: null
);
}, [delayBlocks, hash, votingBlocks]);
}, [api, delayBlocks, hash, votingBlocks]);
return (
<>

View File

@ -17,7 +17,7 @@ export default function useCounter (): number {
(proposals?.length || 0) +
(referenda?.length || 0)
);
}, [proposals, referenda]);
}, [mountedRef, proposals, referenda]);
return counter;
}

View File

@ -20,11 +20,13 @@ function Entry (): React.ReactElement<{}> | null {
const [stateValue, setStateValue] = useState<string | undefined>(value);
useEffect((): void => {
if (value && value !== stateValue) {
setStateValue(value);
} else if (!stateValue && bestNumber) {
setStateValue(bestNumber.toString());
}
setStateValue((stateValue) =>
value && value !== stateValue
? value
: !stateValue && bestNumber
? bestNumber.toString()
: stateValue
);
}, [bestNumber, value]);
if (!stateValue) {

View File

@ -352,6 +352,7 @@ function Forks ({ className }: Props): React.ReactElement<Props> | null {
_subFinHead && _subFinHead();
_subNewHead && _subNewHead();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!tree) {

View File

@ -51,6 +51,7 @@ function NodeInfo (): React.ReactElement<{}> {
return (): void => {
window.clearInterval(timerId);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (

View File

@ -55,7 +55,7 @@ function Transfer ({ assets, className, onClose, recipientId: propRecipientId, s
? api.tx.genericAsset.transfer(assetId, recipientId, amount)
: null
);
}, [amount, assetId, recipientId, senderId]);
}, [api, amount, assetId, recipientId, senderId]);
const _onAddAssetId = (id: string): void => {
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec

View File

@ -106,6 +106,7 @@ function Playground ({ className }: Props): React.ReactElement<Props> {
setIsCustomExample((selected && selected.type === 'custom') || false);
setOptions(options);
setSelected(selected || snippets[0]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect((): void => {

View File

@ -56,7 +56,7 @@ function Register ({ nextFreeId = ONE_THOUSAND, sudoKey }: Props): React.ReactEl
(): ParaInfo => {
return createType(api.registry, 'ParaInfo', { scheduling });
},
[scheduling]
[api, scheduling]
);
const extrinsic = useMemo(
(): SubmittableExtrinsic | null => {
@ -72,7 +72,7 @@ function Register ({ nextFreeId = ONE_THOUSAND, sudoKey }: Props): React.ReactEl
return null;
}
},
[id, info, code, initialHeadState]
[api, id, info, code, initialHeadState]
);
return (

View File

@ -37,7 +37,7 @@ function Deregister ({ id, info, isOpen, onClose, onSubmit, sudoKey }: Props): R
return null;
}
},
[id]
[api, id]
);
const onStart = (): void => {

View File

@ -39,7 +39,7 @@ function ParachainsApp ({ basePath }: Props): React.ReactElement<Props> {
}]
: []
)
], [location, paraInfoRef.current, t]);
], [match, t]);
return (
<main>

View File

@ -31,7 +31,7 @@ function Extensions (): React.ReactElement {
.then(() => toggleBusy());
}
},
[extensions, metaDef, selectedIndex]
[extensions, metaDef, selectedIndex, toggleBusy]
);
if (!options.length) {

View File

@ -71,6 +71,9 @@ function SelectUrl ({ className, onChange }: Props): React.ReactElement<Props> {
useEffect((): void => {
onChange && info.isValid && onChange(info.url);
// the issue here is that the onChange callback changes each and every render... so Houston, we have
// a desperate issue here :(
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [info]);
const _onChangeUrl = useCallback(
@ -87,7 +90,7 @@ function SelectUrl ({ className, onChange }: Props): React.ReactElement<Props> {
),
isCustom
}),
[]
[info]
);
return (

View File

@ -63,7 +63,7 @@ function InjectKeys ({ onClose }: Props): React.ReactElement<Props> | null {
rpc: { section: 'author', method: 'insertKey' } as any,
values: [keyType, suri, publicKey]
}),
[keyType, publicKey, suri]
[keyType, publicKey, queueRpc, suri]
);
const _cryptoOptions = useMemo(
() => CRYPTO_MAP[keyType].map((value): { text: string; value: KeypairType } => ({

View File

@ -33,12 +33,10 @@ function ValidateAmount ({ accountId, onError, value }: Props): React.ReactEleme
newError = t('The specified value is greater than your free balance. The node will bond the maximum amount available.');
}
if (error !== newError) {
onError(newError);
setError(newError);
}
onError(newError);
setError((error) => error !== newError ? newError : error);
}
}, [allBalances, value]);
}, [allBalances, onError, t, value]);
if (!error) {
return null;

View File

@ -49,12 +49,10 @@ function ValidateController ({ accountId, controllerId, defaultController, onErr
newError = t('A controller account should not be set to manages multiple stashes. The selected controller is already controlling {{stashId}}', { replace: { stashId } });
}
if (error !== newError) {
onError(newError);
setError(newError);
}
onError(newError);
setError((error) => error !== newError ? newError : error);
}
}, [accountId, bondedId, controllerId, defaultController, stashId]);
}, [accountId, bondedId, controllerId, defaultController, onError, stashId, t]);
if (!error || !accountId) {
return null;

View File

@ -21,17 +21,15 @@ function ValidateSessionEd25519 ({ onError, sessionId, stashId }: Props): React.
const [error, setError] = useState<string | null>(null);
useEffect((): void => {
let newError = null;
let newError: string | null = null;
if (sessionId === stashId) {
newError = t('For fund security, your session key should not match your stash key.');
}
if (error !== newError) {
onError(newError);
setError(newError);
}
}, [sessionId, stashId]);
onError(newError);
setError((error) => error !== newError ? newError : error);
}, [onError, sessionId, stashId, t]);
if (!error) {
return null;

View File

@ -19,7 +19,7 @@ function InputValidationUnstakeThreshold ({ onError, unstakeThreshold }: Props):
useEffect((): void => {
if (unstakeThreshold) {
let newError = null;
let newError: string | null = null;
if (unstakeThreshold.ltn(0)) {
newError = t('The Threshold must be a positive number');
@ -27,12 +27,10 @@ function InputValidationUnstakeThreshold ({ onError, unstakeThreshold }: Props):
newError = t('The Threshold must lower than 11');
}
if (newError !== error) {
onError(newError);
setError(newError);
}
onError(newError);
setError((error) => error !== newError ? newError : error);
}
}, [unstakeThreshold]);
}, [onError, t, unstakeThreshold]);
if (!error) {
return null;

View File

@ -146,7 +146,7 @@ function Account ({ allStashes, className, isOwnStash, next, onUpdateType, rewar
onUpdateType(stashId, 'other');
}
}
}, [allStashes, stakingAccount, stashId, validateInfo]);
}, [allAccounts, allStashes, onUpdateType, stakingAccount, stashId, validateInfo]);
useEffect((): void => {
nominees && setActiveNoms(
@ -167,7 +167,7 @@ function Account ({ allStashes, className, isOwnStash, next, onUpdateType, rewar
accountId: controllerId,
extrinsic: createPayout(api, payoutRewards)
}),
[api, controllerId, payoutRewards]
[api, controllerId, payoutRewards, queueExtrinsic]
);
return (

View File

@ -43,7 +43,7 @@ export default function useInactives (stashId: string, nominees?: string[]): str
return (): void => {
unsub && unsub();
};
}, [indexes, nominees, stashId]);
}, [api, indexes, mountedRef, nominees, stashId]);
return inactives;
}

View File

@ -14,7 +14,7 @@ interface Props {
function Favorite ({ address, isFavorite, toggleFavorite }: Props): React.ReactElement<Props> {
const _onFavorite = useCallback(
(): void => toggleFavorite(address),
[address]
[address, toggleFavorite]
);
return (

View File

@ -115,13 +115,13 @@ function Address ({ address, className, filterName, hasQueries, isAuthor, isElec
setNominators && setNominators(info.nominators.map(([who]): string => who.toString()));
setStakingState(info);
}
}, [stakingInfo]);
}, [setNominators, stakingInfo, withNominations]);
useEffect((): void => {
setIsVisible(
checkVisibility(api, address, filterName, info)
);
}, [address, filterName, info]);
}, [address, api, filterName, info]);
const _onQueryStats = useCallback(
(): void => {

View File

@ -62,7 +62,7 @@ function ChartRewards ({ validatorId }: Props): React.ReactElement<Props> {
const { currency, divisor } = useMemo((): { currency: string; divisor: BN } => ({
currency: formatBalance.getDefaults().unit,
divisor: new BN('1'.padEnd(formatBalance.getDefaults().decimals + 1, '0'))
}), [formatBalance]);
}), []);
const legends = useMemo(() => [
t('{{currency}} slashed', { replace: { currency } }),
t('{{currency}} rewards', { replace: { currency } }),
@ -73,7 +73,7 @@ function ChartRewards ({ validatorId }: Props): React.ReactElement<Props> {
erasRewards && ownSlashes && stakerPoints && setChart(
extractRewards(erasRewards, ownSlashes, stakerPoints, divisor)
);
}, [erasRewards, stakerPoints]);
}, [divisor, erasRewards, ownSlashes, stakerPoints]);
return (
<div className='staking--Chart'>

View File

@ -54,7 +54,7 @@ function ChartStake ({ validatorId }: Props): React.ReactElement<Props> {
const { currency, divisor } = useMemo((): { currency: string; divisor: BN } => ({
currency: formatBalance.getDefaults().unit,
divisor: new BN('1'.padEnd(formatBalance.getDefaults().decimals + 1, '0'))
}), [formatBalance]);
}), []);
const legends = useMemo(() => [
t('{{currency}} clipped', { replace: { currency } }),
t('{{currency}} total', { replace: { currency } }),
@ -65,7 +65,7 @@ function ChartStake ({ validatorId }: Props): React.ReactElement<Props> {
ownExposure && setChart(
extractStake(ownExposure, divisor)
);
}, [ownExposure]);
}, [divisor, ownExposure]);
return (
<div className='staking--Chart'>

View File

@ -31,11 +31,11 @@ export default function useBlockCounts (accountId: string, sessionRewards: Sessi
});
}
}
}, [accountId, sessionRewards]);
}, [accountId, api, mountedRef, sessionRewards]);
useEffect((): void => {
setCounts([...historic, current || api.createType('u32')].slice(1));
}, [current, historic]);
}, [api, current, historic]);
return counts;
}

View File

@ -195,7 +195,7 @@ function Targets ({ className }: Props): React.ReactElement<Props> {
? !sortFromMax
: true
})),
[sortBy]
[]
);
useEffect((): void => {

View File

@ -31,8 +31,8 @@
"homepage": "https://github.com/polkadot-js/ui/tree/master/packages/ui-reactive#readme",
"dependencies": {
"@babel/runtime": "^7.9.2",
"@polkadot/api": "^1.9.0-beta.11",
"@polkadot/extension-dapp": "^0.23.0-beta.10",
"@polkadot/api": "^1.9.0-beta.12",
"@polkadot/extension-dapp": "^0.23.1",
"rxjs-compat": "^6.5.4"
}
}

View File

@ -172,6 +172,7 @@ function Api ({ children, url }: Props): React.ReactElement<Props> | null {
.catch((error) => console.error(error));
setIsApiInitialized(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!props.isApiInitialized) {

View File

@ -199,7 +199,7 @@ function AccountName ({ children, className, defaultName, label, onClick, overri
} else {
setName(defaultOrAddr(defaultName, cacheAddr, accountIndex));
}
}, [address, info, isRegistrar, toggle]);
}, [api, address, defaultName, info, isRegistrar, t, toggle, toggleJudgement]);
return (
<>
@ -263,9 +263,5 @@ export default React.memo(styled(AccountName)`
line-height: 1em;
vertical-align: middle;
}
.ui--Badge {
margin-top: -2px;
}
}
`);

View File

@ -87,7 +87,7 @@ function ChartHorizBar ({ aspectRatio = 8, className, max = 100, showLabels = fa
if (newJsonValues !== jsonValues) {
setState(calculateOptions(aspectRatio, values, newJsonValues, max, showLabels));
}
}, [values]);
}, [aspectRatio, jsonValues, max, showLabels, values]);
if (!chartData) {
return null;

View File

@ -82,7 +82,7 @@ function LineChart ({ className, colors, labels, legends, style, values }: LineP
if (newJsonValues !== jsonValues) {
setState(calculateOptions(colors, legends, labels, values, newJsonValues));
}
}, [labels, legends, values]);
}, [colors, jsonValues, labels, legends, values]);
if (!chartData) {
return null;

View File

@ -56,6 +56,7 @@ function BaseDropdown<Option> ({ allowAdd = false, className, defaultValue, drop
lastUpdate.current = json;
setStored(value);
onChange && onChange(
transform
? transform(value)
@ -68,7 +69,7 @@ function BaseDropdown<Option> ({ allowAdd = false, className, defaultValue, drop
useEffect((): void => {
_setStored(isUndefined(value) ? defaultValue : value);
}, [defaultValue, value]);
}, [_setStored, defaultValue, value]);
const _onAdd = (_: React.SyntheticEvent<HTMLElement>, { value }: DropdownProps): void =>
onAdd && onAdd(value);

View File

@ -50,6 +50,7 @@ function Editor ({ className, code, isValid, onEdit }: Props): React.ReactElemen
});
editorRef.current = editor;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect((): void => {

View File

@ -61,7 +61,7 @@ function ExtrinsicDisplay ({ defaultValue, isDisabled, isError, isPrivate, label
}
onChange(method);
}, [extrinsic, values]);
}, [extrinsic, onChange, values]);
const _onChangeMethod = (fn: SubmittableExtrinsicFunction<'promise'>): void => setCall({ fn, params: getParams(fn) });

View File

@ -21,16 +21,16 @@ interface Props extends BareProps {
}
function SelectMethod ({ api, className, isError, onChange, options, style, value }: Props): React.ReactElement<Props> | null {
if (!options.length) {
return null;
}
const transform = useCallback(
(method: string): SubmittableExtrinsicFunction<'promise'> =>
api.tx[value.section][method],
[value]
[api, value]
);
if (!options.length) {
return null;
}
return (
<Dropdown
className={classes('ui--DropdownLinked-Items', className)}

View File

@ -206,15 +206,19 @@ function InputNumber (props: Props): React.ReactElement<Props> {
useEffect((): void => {
propsValue && isNewPropsValue(propsValue, value, valueBn) && setValues(getValues(propsValue, si, props));
// ummmm...
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [propsValue]);
useEffect((): void => {
setValues(getValues(value, si, props));
// ummmm...
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value, si, bitLength, maxValue]);
useEffect((): void => {
onChange && onChange(valueBn);
}, [valueBn]);
}, [onChange, valueBn]);
const _onChange = (input: string): void => {
setValues(getValuesFromString(input, si, props));

View File

@ -46,6 +46,7 @@ function Tooltip ({ className, effect = 'solid', offset, place = 'top', text, tr
rootElement.removeChild(tooltipContainer);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return ReactDOM.createPortal(

View File

@ -38,7 +38,7 @@ function TreasuryProposal ({ className, asInset, insetProps, onClick, proposal,
} else {
setProposal(proposal || null);
}
}, [proposal, proposalId]);
}, [api, proposal, proposalId]);
if (!stateProposal) {
return null;

View File

@ -54,7 +54,7 @@ function VoteValue ({ accountId, autoFocus, onChange }: Props): React.ReactEleme
// only do onChange to parent when the BN value comes in, not our formatted version
useEffect((): void => {
isBn(value) && onChange(value);
}, [value]);
}, [onChange, value]);
const _setValue = useCallback(
(value?: BN): void => setValue({ selectedId, value }),

View File

@ -20,4 +20,3 @@ export { default as usePassword } from './usePassword';
export { default as useRegistrars } from './useRegistrars';
export { default as useSudo } from './useSudo';
export { default as useToggle } from './useToggle';
export { default as useTx } from './useTx';

View File

@ -8,13 +8,14 @@ import { useCallback, useState } from 'react';
export default function useAccountId (initialValue: StringOrNull = null, onChangeAccountId?: (_: StringOrNull) => void): [StringOrNull, (_: StringOrNull) => void] {
const [accountId, setAccountId] = useState<StringOrNull>(initialValue);
const _setAccountId = useCallback(
(accountId: StringOrNull = null): void => {
setAccountId(accountId);
onChangeAccountId && onChangeAccountId(accountId);
},
[]
[onChangeAccountId]
);
return [accountId, _setAccountId];

View File

@ -29,6 +29,7 @@ export default function useAccounts (): UseAccounts {
return (): void => {
setTimeout(() => subscription.unsubscribe(), 0);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return state;

View File

@ -29,6 +29,7 @@ export default function useAccounts (): UseAccounts {
return (): void => {
setTimeout(() => subscription.unsubscribe(), 0);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return state;

View File

@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { useCallback } from 'react';
import { useCallback, useMemo } from 'react';
import store from 'store';
import useApi from './useApi';
@ -10,9 +10,12 @@ import useApi from './useApi';
// create a chain-specific key for the local cache
export default function useCacheKey <T> (storageKeyBase: string): [(defaultValue?: T) => T | undefined, (value: T) => T] {
const { api, isDevelopment } = useApi();
const STORAGE_KEY = `${storageKeyBase}:${isDevelopment ? 'development' : api.genesisHash}`;
const getter = useCallback((): T | undefined => store.get(STORAGE_KEY), []);
const setter = useCallback((value: T): T => store.set(STORAGE_KEY, value), []);
const storageKey = useMemo(
() => `${storageKeyBase}:${isDevelopment ? 'development' : api.genesisHash}`,
[api, isDevelopment, storageKeyBase]
);
const getter = useCallback((): T | undefined => store.get(storageKey), [storageKey]);
const setter = useCallback((value: T): T => store.set(storageKey, value), [storageKey]);
return [getter, setter];
}

View File

@ -125,7 +125,7 @@ export default function useCall <T> (fn: TrackFn | undefined | null | false, par
subscribe(mountedRef, tracker, fn, mappedParams, setValue, options);
}
}
}, [fn, params]);
}, [fn, options, mountedRef, params]);
return value;
}

View File

@ -6,21 +6,23 @@ import { useState, useEffect } from 'react';
import useIsMountedRef from './useIsMountedRef';
const DEFAULT_DELAY = 250;
// Debounces inputs
export default function useDebounce <T> (value: T, delay = 250): T {
export default function useDebounce <T> (value: T, delay?: number): T {
const mountedRef = useIsMountedRef();
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect((): () => void => {
const handler = setTimeout(() => {
mountedRef.current && setDebouncedValue(value);
}, delay);
}, delay || DEFAULT_DELAY);
// each time it renders, it clears
return (): void => {
clearTimeout(handler);
};
}, [value]);
}, [delay, value, mountedRef]);
return debouncedValue;
}

View File

@ -10,6 +10,7 @@ import useCacheKey from './useCacheKey';
export default function useFavorites (storageKeyBase: string): [string[], (address: string) => void] {
const [getCache, setCache] = useCacheKey<string[]>(storageKeyBase);
const [favorites, setFavorites] = useState<string[]>(getCache() || []);
const toggleFavorite = useCallback(
(address: string): void => setFavorites(
(favorites: string[]) => setCache(
@ -18,7 +19,7 @@ export default function useFavorites (storageKeyBase: string): [string[], (addre
: [...favorites, address]
)
),
[]
[setCache]
);
return [favorites, toggleFavorite];

View File

@ -18,7 +18,7 @@ export default function useFormField<T> (defaultValue: T | null, validate: (_?:
const [value, setValue] = useState<T | null>(defaultValue);
const isValid = useMemo(
(): boolean => isTruthy<T>(value) && validate(value),
[value]
[validate, value]
);
const setter = useCallback((value?: T | null): void => setValue(value || null), []);

View File

@ -4,9 +4,7 @@
import { useEffect, useRef } from 'react';
export interface MountedRef {
current: boolean;
}
export type MountedRef = React.MutableRefObject<boolean>;
export default function useIsMountedRef (): MountedRef {
const isMounted = useRef(false);

View File

@ -18,10 +18,12 @@ export default function useMembers (collective: 'council' | 'technicalCommittee'
const [state, setState] = useState<Result>({ isMember: false, members: [] });
const retrieved = (
collective === 'council'
// eslint-disable-next-line react-hooks/rules-of-hooks
? useCall<string[]>(api.query.electionsPhragmen?.members || api.query.elections.members, [], {
transform: (accounts: [AccountId, Balance][]): string[] =>
accounts.map(([accountId]) => accountId.toString())
})
// eslint-disable-next-line react-hooks/rules-of-hooks
: useCall<string[]>(api.query.technicalCommittee.members, [], {
transform: (accounts: AccountId[]): string[] =>
accounts.map((accountId) => accountId.toString())

View File

@ -16,7 +16,7 @@ export default function useModal (defaultIsOpen?: boolean, onOpen?: () => void,
onOpen && onOpen();
},
[]
[onOpen, setIsOpen]
);
const _onClose = useCallback(
(): void => {
@ -24,7 +24,7 @@ export default function useModal (defaultIsOpen?: boolean, onOpen?: () => void,
onClose && onClose();
},
[]
[onClose, setIsOpen]
);
return { isOpen, onClose: _onClose, onOpen: _onOpen };

View File

@ -41,7 +41,7 @@ function useNextPayouts (onlyLatest?: boolean): [string, BN][] | undefined {
: lastPayouts
);
}
}, [allInfo, indexes, onlyLatest, stashIds]);
}, [allInfo, indexes, mountedRef, onlyLatest, stashIds]);
return nextPayouts;
}
@ -75,7 +75,7 @@ export default function useOwnEraRewards (onlyLatest?: boolean): OwnRewards {
mountedRef.current && available && nextPayouts && setState(
getRewards(available, nextPayouts)
);
}, [available, nextPayouts]);
}, [available, mountedRef, nextPayouts]);
return state;
}

View File

@ -44,7 +44,7 @@ export default function useOwnStashes (): [string, IsInKeyring][] | undefined {
mountedRef.current && allAccounts && ownBonded && ownLedger && setState(
getStashes(allAccounts, ownBonded, ownLedger)
);
}, [allAccounts, ownBonded, ownLedger]);
}, [allAccounts, mountedRef, ownBonded, ownLedger]);
return state;
}
@ -58,7 +58,7 @@ export function useOwnStashIds (): string[] | undefined {
mountedRef.current && ownStashes && setStashIds(
ownStashes.map(([stashId]) => stashId)
);
}, [ownStashes]);
}, [mountedRef, ownStashes]);
return stashIds;
}

View File

@ -1,115 +0,0 @@
// Copyright 2017-2020 @polkadot/react-hooks authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { StringOrNull } from '@polkadot/react-components/types';
import { Call } from '@polkadot/types/interfaces';
import { ExtrinsicAndSenders, TxDef, TxDefs, TxSource, TxProps, TxState } from './types';
import { useContext, useMemo, useState } from 'react';
import { ApiPromise } from '@polkadot/api';
import { StatusContext } from '@polkadot/react-components/Status';
import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
import { assert, isFunction } from '@polkadot/util';
import useApi from './useApi';
function getExtrinsic<T extends TxDefs> (api: ApiPromise, txDef: T): SubmittableExtrinsic | null {
if (!txDef) {
return null;
}
if (Array.isArray(txDef)) {
const [section, method] = txDef[0].split('.');
assert(api.tx[section] && api.tx[section][method], `Unable to find api.tx.${section}.${method}`);
try {
return api.tx[section][method](...(
isFunction(txDef[1])
? txDef[1]()
: txDef[1]
)) as any as SubmittableExtrinsic;
} catch (e) {
return null;
}
} else {
if ((txDef as Call).callIndex) {
const fn = api.findCall(txDef.callIndex);
return api.tx[fn.section][fn.method](...txDef.args);
}
return txDef as any as SubmittableExtrinsic;
}
}
export default function useTx<T extends TxDef> (memoFn: (...args: any[]) => TxSource<T>, memoArr: any[], { accountId: anAccountId, onChangeAccountId, onStart, onSuccess, onFailed, onUpdate }: TxProps = {}): TxState {
const { queueExtrinsic } = useContext(StatusContext);
const { api } = useApi();
const txSource = useMemo(memoFn, memoArr);
const [accountId, setAccountId] = useState<StringOrNull>(anAccountId || null);
const [isSending, setIsSending] = useState(false);
const _onStart = (): void => {
setIsSending(true);
onStart && onStart();
};
const _onSuccess = (): void => {
setIsSending(false);
onSuccess && onSuccess();
};
const _onFailed = (): void => {
setIsSending(false);
onFailed && onFailed();
};
const _onUpdate = (): void => {
setIsSending(false);
onUpdate && onUpdate();
};
function getExtrinsicAndSenders (api: ApiPromise, accountId: StringOrNull, [txDef, isSubmittable]: TxSource<T>): ExtrinsicAndSenders {
const extrinsic = getExtrinsic<T>(api, txDef);
function _sendTx (isUnsigned?: boolean): void {
!!extrinsic && isSubmittable && queueExtrinsic({
accountId,
extrinsic,
isUnsigned,
txFailedCb: _onFailed,
txStartCb: _onStart,
txSuccessCb: _onSuccess,
txUpdateCb: _onUpdate
});
}
return {
extrinsic,
isSubmittable: !!accountId && !!extrinsic && isSubmittable,
sendTx: (): void => _sendTx(),
sendUnsigned: (): void => _sendTx(true)
};
}
const txAndSenders = useMemo<ExtrinsicAndSenders>(
(): ExtrinsicAndSenders => getExtrinsicAndSenders(api, accountId, txSource),
[accountId, txSource]
);
return {
...txAndSenders,
isSending,
accountId,
onChangeAccountId: (accountId: StringOrNull): void => {
setAccountId(accountId);
onChangeAccountId && onChangeAccountId(accountId);
}
};
}

View File

@ -18,7 +18,7 @@ function Balance ({ className, defaultValue: { value }, isDisabled, isError, lab
isValid: !isError && !!value,
value
}),
[isError]
[isError, onChange]
);
return (

View File

@ -55,7 +55,7 @@ function BaseBytes ({ asHex, children, className, defaultValue: { value }, isDis
: true
)
);
}, [length, value]);
}, [length, validate, value]);
const _onChange = useCallback(
(hex: string): void => {
@ -80,7 +80,7 @@ function BaseBytes ({ asHex, children, className, defaultValue: { value }, isDis
setIsValid(isValid);
},
[withLength]
[asHex, length, onChange, validate, withLength]
);
const defaultValue = value

View File

@ -25,7 +25,7 @@ function Bytes ({ className, defaultValue, isDisabled, isError, label, name, onC
value: Compact.addLengthPrefix(value)
});
},
[]
[onChange]
);
return !isDisabled && isFileDrop

View File

@ -38,7 +38,7 @@ function ErrorDisplay (props: Props): React.ReactElement<Props> {
setDetails({ details: null });
}
}
}, [props.defaultValue]);
}, [details, props.defaultValue]);
if (!props.isDisabled || !details) {
return <Unknown {...props} />;

View File

@ -70,7 +70,7 @@ function EnumParam (props: Props): React.ReactElement<Props> {
value: { [current[0].name as string]: value }
});
},
[current]
[current, onChange]
);
if (isDisabled) {

View File

@ -48,7 +48,7 @@ function KeyValue ({ className, isDisabled, label, onChange, onEnter, style, wit
value.u8a
)
});
}, [key, value]);
}, [key, onChange, value]);
const _onChangeKey = useCallback(
(key: string): void => setKey(createParam(key)),

View File

@ -77,7 +77,7 @@ function KeyValueArray ({ className, defaultValue, isDisabled, isError, label, o
onChange && onChange(encoded);
},
[]
[onChange, t]
);
if (isDisabled) {

View File

@ -12,7 +12,7 @@ function Null ({ onChange }: Props): React.ReactElement<Props> | null {
isValid: true,
value: null
});
}, []);
}, [onChange]);
return null;
}

View File

@ -21,7 +21,7 @@ function Option ({ className, defaultValue, isDisabled, name, onChange, onEnter,
isValid: true,
value: null
});
}, [isActive]);
}, [isActive, onChange]);
return (
<div className={className}>

View File

@ -29,10 +29,6 @@ function StructParam (props: Props): React.ReactElement<Props> {
setParams((typeDef.sub as TypeDef[]).map((type): ParamDef => ({ name: type.name, type })));
}, [type]);
if (isDisabled) {
return <Static {...props} />;
}
const _onChangeParams = useCallback(
(values: RawParam[]): void => {
onChange && onChange({
@ -44,9 +40,13 @@ function StructParam (props: Props): React.ReactElement<Props> {
}, {})
});
},
[params]
[params, onChange]
);
if (isDisabled) {
return <Static {...props} />;
}
return (
<div className='ui--Params-Struct'>
<Base

View File

@ -28,10 +28,6 @@ function Tuple (props: Props): React.ReactElement<Props> {
}
}, [type]);
if (isDisabled) {
return <Static {...props} />;
}
const _onChangeParams = useCallback(
(values: RawParam[]): void => {
onChange && onChange({
@ -39,9 +35,13 @@ function Tuple (props: Props): React.ReactElement<Props> {
value: values.map(({ value }): any => value)
});
},
[]
[onChange]
);
if (isDisabled) {
return <Static {...props} />;
}
return (
<div className='ui--Params-Tuple'>
<Base

View File

@ -78,7 +78,7 @@ function Vector ({ className, defaultValue, isDisabled = false, label, onChange,
isValid: values.reduce((result: boolean, { isValid }): boolean => result && isValid, true),
value: values.map(({ value }): any => value)
});
}, [values]);
}, [values, onChange]);
const _rowAdd = useCallback(
(): void => setCount((count) => count + 1),

View File

@ -24,8 +24,9 @@ interface Props {
function ParamComp ({ defaultValue, index, isDisabled, name, onChange, onEnter, onEscape, overrides, type }: Props): React.ReactElement<Props> {
const _onChange = useCallback(
(value: RawParamOnChangeValue): void => onChange(index, value),
[index]
(value: RawParamOnChangeValue): void =>
onChange(index, value),
[index, onChange]
);
return (

View File

@ -74,6 +74,7 @@ function BlockAuthorsBase ({ children }: Props): React.ReactElement<Props> {
}
});
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (

View File

@ -48,6 +48,7 @@ function EventsBase ({ children }: Props): React.ReactElement<Props> {
}
});
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (

View File

@ -16,7 +16,7 @@ interface Props {
}
function ContractCall ({ endowment, fees, onChange }: Props): React.ReactElement<Props> | null {
const [state, setState] = useState<State>({
const [, setState] = useState<State>({
extraFees: new BN(0),
extraAmount: new BN(0),
extraWarn: false
@ -34,12 +34,14 @@ function ContractCall ({ endowment, fees, onChange }: Props): React.ReactElement
extraWarn: false
};
if (!update.extraAmount.eq(state.extraAmount) || !update.extraFees.eq(state.extraFees)) {
onChange(update);
}
setState((state): State => {
if (!update.extraAmount.eq(state.extraAmount) || !update.extraFees.eq(state.extraFees)) {
onChange(update);
}
setState(update);
}, [endowment, fees]);
return update;
});
}, [endowment, fees, onChange]);
return null;
}

View File

@ -16,7 +16,7 @@ interface Props {
}
function ContractDeploy ({ endowment, fees, onChange }: Props): React.ReactElement<Props> | null {
const [state, setState] = useState<State>({
const [, setState] = useState<State>({
extraFees: new BN(0),
extraAmount: new BN(0),
extraWarn: false
@ -34,12 +34,14 @@ function ContractDeploy ({ endowment, fees, onChange }: Props): React.ReactEleme
extraWarn: false
};
if (!update.extraAmount.eq(state.extraAmount) || !update.extraFees.eq(state.extraFees)) {
onChange(update);
}
setState((state): State => {
if (!update.extraAmount.eq(state.extraAmount) || !update.extraFees.eq(state.extraFees)) {
onChange(update);
}
setState(update);
}, [endowment, fees]);
return update;
});
}, [endowment, fees, onChange]);
return null;
}

View File

@ -57,7 +57,7 @@ function Proposal ({ deposit, onChange }: Props): React.ReactElement<Props> {
isBelowMinimum
});
}
}, [minDeposit]);
}, [deposit, minDeposit, onChange]);
return (
<>

View File

@ -61,7 +61,7 @@ function Transfer ({ amount, fees, onChange, recipientId }: Props): React.ReactE
isNoEffect
});
}
}, [amount, allBalances, fees]);
}, [amount, allBalances, fees, onChange]);
return (
<>

923
yarn.lock

File diff suppressed because it is too large Load Diff